# swing up of the inverted dual pendulum with partial linearization

# import trajectory class and necessary dependencies
from pytrajectory import TransitionProblem
from sympy import cos, sin
import numpy as np

def f(xx, uu, uuref, t, pp):
    """ Right hand side of the vectorfield defining the system dynamics

    :param xx:       state
    :param uu:       input
    :param uuref:    reference input (not used)
    :param t:        time (not used)
    :param pp:       additionial free parameters  (not used)

    :return:        xdot
    """
    x1, x2, x3, x4, x5, x6 = xx  # system variables
    uu, = uu                      # input variable

    # length of the pendulums
    l1 = 0.7
    l2 = 0.5

    g = 9.81    # gravitational acceleration

    ff = np.array([x2,
                   uu,
                   x4,
                   (1/l1)*(g*sin(x3) + uu*cos(x3)),
                   x6,
                   (1/l2)*(g*sin(x5) + uu*cos(x5))
                   ])

    return ff

# system state boundary values for a = 0.0 [s] and b = 2.0 [s]
xa = [0.0, 0.0,  np.pi, 0.0,  np.pi, 0.0]
xb = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

# boundary values for the input
ua = [0.0]
ub = [0.0]

# create trajectory object
S = TransitionProblem(f, a=0.0, b=2.0, xa=xa, xb=xb, ua=ua, ub=ub)

# alter some method parameters to increase performance
S.set_param('su', 10)
S.set_param('eps', 8e-2)

# run iteration
S.solve()

# the following code provides an animation of the system above
# for a more detailed explanation have a look at the 'Visualisation' section in the documentation
import sys
import matplotlib as mpl
from pytrajectory.visualisation import Animation

def draw(xti, image):
    x, phi1, phi2 = xti[0], xti[2], xti[4]

    l1 = 0.7
    l2 = 0.5

    car_width = 0.05
    car_heigth = 0.02
    pendel_size = 0.015

    x_car = x
    y_car = 0

    x_pendel1 = -l1*sin(phi1)+x_car
    y_pendel1 = l1*cos(phi1)

    x_pendel2 = -l2*sin(phi2)+x_car
    y_pendel2 = l2*cos(phi2)

    # pendulums
    sphere1 = mpl.patches.Circle((x_pendel1,y_pendel1),pendel_size,color='k')
    sphere2 = mpl.patches.Circle((x_pendel2,y_pendel2),pendel_size,color='0.3')

    # car
    car = mpl.patches.Rectangle((x_car-0.5*car_width,y_car-car_heigth),car_width,car_heigth,fill=True,facecolor='0.75',linewidth=2.0)

    # joint
    joint = mpl.patches.Circle((x_car,0),0.005,color='k')

    # rods
    rod1 = mpl.lines.Line2D([x_car,x_pendel1],[y_car,y_pendel1],color='k',zorder=1,linewidth=2.0)
    rod2 = mpl.lines.Line2D([x_car,x_pendel2],[y_car,y_pendel2],color='0.3',zorder=1,linewidth=2.0)

    image.patches.append(sphere1)
    image.patches.append(sphere2)
    image.patches.append(car)
    image.patches.append(joint)
    image.lines.append(rod1)
    image.lines.append(rod2)

    return image

if not 'no-pickle' in sys.argv:
    # here we save the simulation results so we don't have to run
    # the iteration again in case the following fails
    S.save(fname='ex2_InvertedDualPendulumSwingUp.pcl')

if 'plot' in sys.argv or 'animate' in sys.argv:
    A = Animation(drawfnc=draw, simdata=S.sim_data,
                  plotsys=[(0,'x'),(2,'phi1'),(4,'phi2')], plotinputs=[(0,'u')])

    xmin = np.min(S.sim_data[1][:,0])
    xmax = np.max(S.sim_data[1][:,0])
    A.set_limits(xlim=(xmin - 1.0, xmax + 1.0), ylim=(-0.8,0.8))

if 'plot' in sys.argv:
    A.show(t=S.b)

if 'animate' in sys.argv:
    A.animate()
    A.save('ex2_InvertedDualPendulumSwingUp.gif')


import csv
from matplotlib import pyplot
import numpy as np
import os
import sys

xmin, xmax = np.float64(-2.5), np.float64(1.0)
ymin, ymax = np.float64(-1.25), np.float64(1.25)
nx = int(os.environ.get('INPUTMAT_ROWS'))
ny = int(os.environ.get('INPUTMAT_COLS'))
maxiter = 100

def mandelbrotorbit(c):
    z = complex(0, 0) + c
    for n in range(maxiter):
        if abs(z) > 2:
            return n
        z = z*z + c
    return maxiter

def linspace(start, end, n):
    lspace = [start]
    distance = (end - start) / np.float64(n - 1)
    next = start
    while np.abs(next - end) > 0.000000000001:
        next += distance
        lspace.append(next)
    return lspace

X = linspace(xmin, xmax, nx)
Y = linspace(ymin, ymax, ny)

def mandelbrot(x):
    Z = [complex(x, y) for y in Y]
    return list(map(mandelbrotorbit, Z))

N = list(map(mandelbrot, X))

def matrix_to_csv(m):
    filename = 'output-py-seq-{}.csv'.format(nx)
    with open(filename, 'w') as f:
        writer = csv.writer(f, delimiter=';', lineterminator='\n')
        writer.writerows(N)

if '-export' in sys.argv:
    matrix_to_csv(N)

if '-fractal' in sys.argv:
    pyplot.imshow(N)
    pyplot.savefig('mandel.png')


import numpy as np

def unit_vector(vector):
    """ Returns the unit vector of the vector.  """
    return vector / np.linalg.norm(vector)

def angle_between(v1, v2):
    """ Returns the angle in radians between vectors 'v1' and 'v2'::

            >>> angle_between((1, 0, 0), (0, 1, 0))
            1.5707963267948966
            >>> angle_between((1, 0, 0), (1, 0, 0))
            0.0
            >>> angle_between((1, 0, 0), (-1, 0, 0))
            3.141592653589793
    """
    v1_u = unit_vector(v1)
    v2_u = unit_vector(v2)
    return (180 / np.pi) * np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

def distance_between(v1, v2):
    return np.linalg.norm(np.asarray(v1) - np.asarray(v2))

def heron(a,b,c):
    s = (a + b + c) / 2
    area = (s*(s-a) * (s-b)*(s-c)) ** 0.5
    return area

def area_triangle(v1, v2, v3):
    a = distance_between(v1, v2)
    b = distance_between(v1, v3)
    c = distance_between(v2, v3)
    A = heron(a,b,c)
    return A


import numpy as np
from scipy import signal
import matplotlib.pyplot as plt

T = 5
Fs = 400.0
n = T * Fs

f1 = 10
f2 = 25
f3 = 50
f4 = 100

t1 = np.linspace(0, T, n)
x1 = np.cos(2 * np.pi * f1 * t1)
t2 = np.linspace(T, 2 * T, n)
x2 = np.cos(2 * np.pi * f2 * t2)
t3 = np.linspace(2 * T, 3 * T, n)
x3 = np.cos(2 * np.pi * f3 * t3)
t4 = np.linspace(3 * T, 4 * T, n)
x4 = np.cos(2 * np.pi * f4 * t4)

x = np.hstack((x1, x2, x3, x4))
t = np.hstack((t1, t2, t3, t4))

TWs = [25e-3, 100e-3, 500e-3, 1000e-3, 2000e-3, 3000e-3]
NWs = []
npersegs = []
for TW in TWs:
    NW = int(TW * Fs)
    NWs.append(NW)
    npersegs.append(NW)

windows = []
windows.append(signal.gaussian(NWs[0], std=30))
windows.append(signal.gaussian(NWs[1], std=30))
windows.append(signal.gaussian(NWs[2], std=30))
windows.append(signal.gaussian(NWs[3], std=30))
windows.append(signal.gaussian(NWs[4], std=30))
windows.append(signal.gaussian(NWs[5], std=30))

noverlap = 1
nfft = 2048

print(t.shape, x.shape)

plt.figure()
plt.plot(t, x)

plt.figure()

cnt = 1
for window, nperseg in zip(windows, npersegs):
    # print(window)

    # (x, fs=1.0, window=('tukey', 0.25), nperseg=None, noverlap=None, nfft=None, detrend='constant', return_onesided=True, scaling='density', axis=-1, mode='psd')
    # [F, T, S] = signal.spectral.spectrogram(x, np.hamming(1024), nperseg=1024, noverlap=0, detrend=False, return_onesided=True, mode='magnitude')
    [F, T, S] = signal.spectral.spectrogram(x, fs=Fs, window=window, nperseg=nperseg, noverlap=noverlap,
                                            nfft=nfft, detrend=False, return_onesided=True,
                                            scaling='density', mode='psd')

    print('T: ', T.shape, 'F:', F.shape, 'S:', S.shape)

    plt.subplot(2, 3, cnt)
    map = plt.pcolormesh(T, F, S, cmap='jet')
    plt.ylabel('Frequency/Hz')
    plt.xlabel('Time/s')
    plt.axis('tight')
    plt.title('STFT->' + 'Gauss window size:' + str(TWs[cnt-1]) + 's')
    cb = plt.colorbar(cax=None, ax=None, shrink=0.5)
    cnt = cnt + 1
plt.show()


'''
Shortest Path in a Weighted Directed Graph Using Dynamic Programming.

Solution uses vectorization compared to the solution given in the lecture notes.
'''

import numpy as np
from numpy import inf

Q = np.array([[inf, 1,   5,   3,   inf, inf, inf],
              [inf, inf, inf, 9,   6,   inf, inf],
              [inf, inf, inf, inf, inf, 2,   inf],
              [inf, inf, inf, inf, inf, 4,   8],
              [inf, inf, inf, inf, inf, inf, 4],
              [inf, inf, inf, inf, inf, inf, 1],
              [inf, inf, inf, inf, inf, inf, 0]])

num_nodes = 7
J_n = [0 for _ in range(num_nodes)]
J_next = [0 for _ in range(num_nodes)]
n = 0
i, max_iter = 0, 500

while i < max_iter:
    min_cost = np.min(Q + J_n, axis=1)
    J_next = min_cost

    if np.all(J_next == J_n):
        break

    J_n = J_next
    i += 1

print(J_n)
print(J_n[0])


"""
metric.py
Based on
https://github.com/rshk/elo/blob/master/elo.py
"""

import numpy as np

def elo_expected(A, B):
    """
    Calculate expected score of A in a match against B
    :param A: Elo rating for player A
    :param B: Elo rating for player B
    """
    return 1. / (1 + 10 ** ((B - A) / 400.))

def get_elo_score(old, exp, score, k=32):
    """
    Calculate the new Elo rating for a player
    :param old: The previous Elo rating
    :param exp: The expected score for this match
    :param score: The actual score for this match
    :param k: The k-factor for Elo (default: 32)
    """
    return int(old + k * (score - exp))

def elo(a_score, b_score, results, k=32):
    """
    Return an elo score for players a and b according to results
    a_score : int
        elo score of a
    b_score : int
        elo score of b
    results : x, list of xs, np.ndarray of xs
        where x is in [0, 0.5, 1]
        results from game, where 0 is loss for a,
        0.5 is draw, 1 is win for a. Ignores values that are not above

    Returns
    new_a_score : int
        new elo score for a
    new_b_score : int
        new elo score for b
    """
    # convert to list
    if not isinstance(results, (list, np.ndarray)):
        results = [results]

    new_a_score = a_score
    new_b_score = b_score

    allowed_results = [0, 0.5, 1]
    for result in results:
        if result not in allowed_results: continue

        exp_a = elo_expected(new_a_score, new_b_score)
        exp_b = elo_expected(new_b_score, new_a_score)

        new_a_score = get_elo_score(new_a_score, exp_a, result, k=k)
        new_b_score = get_elo_score(new_b_score, exp_b, 1 - result, k=k)

    return new_a_score, new_b_score


"""
iterfast
====

Provides NumPy Arrays
  1. of some commonly used sequences and series
     eg. A sequence of prime numbers.
  2. in least possible time.
  3. that may take time to achieve manually.

How to use the documentation
----------------------------
Documentation is available in two forms: docstrings provided
with the code, and a loose standing reference guide, available from
`the iterfast homepage <https://https://github.com/pradipbankar0097/iterfast/docs/index.html>`_.

"""

import math
import numpy
def getprimes(upto=1000000):
    """
    Parameters
    ----------
    1. upto : The upper bound for the required sequence of prime numbers.

    Returns
    -------
    A numpy.ndarray of all prime numbers upto 'upto' inclusive.
    """
    if upto<2:
        return numpy.ndarray(0)
    primes=numpy.arange(3,upto+1,2)
    isprime=numpy.ones(int((upto-1)/2),dtype=bool)
    for factor in primes[:int(math.sqrt(upto))]:
        if isprime[int((factor-2)/2)]:
            isprime[int((factor*3-2)/2)::int(factor)]=0
    return numpy.insert(primes[isprime],0,2).copy()


import numpy as np
from scipy.stats import uniform
import random, math
from statistics import mean
import matplotlib.pyplot as plt

# Question 1.2
# Calculate the integral of cos(x)dx in (0, 2) interval
random.seed(0)
f_estimates = [None]*50
f_std = [None]*50

for i in range(1, 51):
    random_array = uniform.rvs(size = i*1000, scale=2)
    f_values_array = np.cos(random_array)*2
    f_estimates[i-1] = np.mean(f_values_array)
    f_std[i-1] = np.std(f_values_array/np.sqrt(i*1000))

# Question 1.3
analytical_value = math.sin(2) - math.sin(0)

plt.plot(f_estimates, ".")
plt.plot(mean(f_estimates)+np.array(f_std)*3)
plt.plot(mean(f_estimates)-np.array(f_std)*3)
plt.plot([analytical_value]*50, "x")
plt.show()


#!/usr/bin/env python3
import sys

import numpy as np

from multiprocessing import cpu_count, Pool

np.random.seed(2017)

def inside_circle(total_count):

    x = np.float32(np.random.uniform(size=total_count))
    y = np.float32(np.random.uniform(size=total_count))

    radii = np.sqrt(x*x + y*y)

    count = len(radii[np.where(radii<=1.0)])

    return count

def estimate_pi(n_samples,n_cores):

    partitions = [ ]
    for i in range(n_cores):
        partitions.append(int(n_samples/n_cores))

    pool = Pool(processes=n_cores)
    counts=pool.map(inside_circle, partitions)

    total_count = sum(partitions)
    return (4.0 * sum(counts) / total_count)

if __name__=='__main__':

    ncores = cpu_count()
    n_samples = 10000
    if len(sys.argv) > 1:
        n_samples = int(sys.argv[1])

    partitions = [ int(n_samples/ncores) for item in range(ncores)]

    sizeof = np.dtype(np.float32).itemsize

    my_pi = estimate_pi(n_samples,ncores)

    print("[parallel version] required memory %.3f MB" % (n_samples*sizeof*3/(1024*1024)))
    print("[using %3i cores ] pi is %f from %i samples" % (ncores,my_pi,n_samples))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jul 10 10:57:16 2021

@author: alessandro
"""

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

u = []
for i in range(1, 36):
    u.append((15 * ((3/5) ** i + 1)) / (5 * (3/5) ** i + 3))

print(u)

u1 = [4]
for i in range(1, 35):
    u1.append(8 - (15 / (u1[i - 1])))
print(u1)

u2 = [4, 17/4]
for i in range(2, 35):
    u2.append(108 - (815 / u2[i - 1]) + (1500 / (u2[i - 1] * u2[i - 2])))
print(u2)

u = np.array(u)
u1 = np.array(u1)
u2 = np.array(u2)

eu1 = np.abs(u1 - u)
eu2 = np.abs(u2 - u)
x = np.arange(1, 36)
plt.semilogy(x, eu1, label="Errore u1")
plt.semilogy(x, eu2, label="Errore u2")
plt.legend()
plt.show()

# Punti fissi
def iterazione(g, x0, tolx, nmax=2048):
    x = g(x0)
    it, xk = 1, [x]
    while it < nmax and abs(x - x0) > tolx * abs(x):
        x0 = x
        x = g(x0)
        xk.append(x)
        it += 1
    return x, it, xk

def g1(x): return 8 - 15/x
def g2(x): return 108 - 815/x + 1500/(x**2)

def dg1(x): return 15 / (x ** 2)
def dg2(x): return 815 / (x ** 2) - 1500 / (x ** 3)

xx = np.arange(1, 110)
plt.title("g1(x)")
plt.ylim(-5, 10)
plt.plot(xx, xx)
plt.plot(xx, g1(xx), label="g1(x)")
plt.plot(xx, dg1(xx), label="g1'(x)")
plt.plot([5], [g1(5)], 'o')
plt.plot([5], [dg1(5)], 'o')
plt.legend()
plt.show()

plt.title("g2(x)")
plt.ylim(-5, 110)
plt.plot(xx, xx)
plt.plot(xx, g2(xx), label="g2(x)")
plt.plot(xx, dg2(xx), label="g2'(x)")
plt.plot([5], [g2(5)], 'o')
plt.plot([5], [dg2(5)], 'o')
plt.plot([100], [g2(100)], 'o')
plt.plot([100], [dg2(100)], 'o')
plt.legend()
plt.show()

punto, it, xk = iterazione(g1, 1, 1e-12)
print(f"g1 => {punto} con {it} iterazioni")

punto, it, xk = iterazione(g2, 1, 1e-12)
print(f"g2 => {punto} con {it} iterazioni")


#! /usr/bin/python3
"""
Generate/plot a spiral.

If output of image is desired, include any string as a
command line argument.

"""
import sys

import matplotlib.pyplot as plt
import numpy as np

#### Spiral Parameters ####
xCenter = 2.0
yCenter = 4.0
nPts = 300
                   # Reverse values for inward spiral
radius0 = 2.0      # Initial radius
radiusF = 5.0      # Final radius
nTurns = 3         # Number of turns
theta0_deg = 45.0  # Initial rotation angle, degrees
###########################

if len(sys.argv) == 2:
    outFlag = True
else:
    outFlag = False

fig = plt.figure(facecolor='lightgrey')
ax = fig.add_subplot(111)
ax.patch.set_facecolor('black')

ax.set_aspect('equal')
ax.grid(True, which='both')

# Insert X and Y axes
ax.axhline(y=0, color='w')
ax.axvline(x=0, color='w')

# Remove #'s to suppress tick labels
#ax.set_xticklabels([])
#ax.set_yticklabels([])

# Radius growth rate across total number of turns
b = (radiusF-radius0)/(2.0*np.pi*nTurns)

# Convert initial rotation angle to radians
theta0 = theta0_deg*np.pi/180.0
# Determine final rotation angle across total number of turns
thetaF = 2.0*np.pi*nTurns + theta0

# Array of incremental angles
t = np.arange(theta0, thetaF, (thetaF-theta0)/nPts, dtype=float)

# (x,y) coordinates of individual points on the spiral
x = (radius0+b*t)*np.cos(t)+xCenter
y = (radius0+b*t)*np.sin(t)+yCenter

print('len(x) = ', len(x))

print('xmin = {0:8.2f}  xmax = {1:8.2f}'.format(min(x), max(x)))
print('ymin = {0:8.2f}  ymax = {1:8.2f}'.format(min(y), max(y)))

# s is marker size
ax.scatter(x, y, marker='o', s=4.0, color='red')

# Conditionally output image
if outFlag == True:
    fig.savefig('genSpiral.png', facecolor='lightgrey', format='png')

plt.show()


# -*- coding: utf-8 -*-
"""
ese9
"""

import numpy as np
import interpolL as fz
import math

def zeri_Cheb(a,b,n):
    t1=(a+b)/2
    t2=(b-a)/2
    x=np.zeros((n+1,))
    for k in range(n+1):
        x[k]=t1+t2*np.cos(((2*k+1)/(2*(n+1))*math.pi))

    return x

#calcolo della costante di lebesgue in [-1,1]

xx=np.linspace(-1,1,200);
LLe = np.zeros((4,1))
LLc = np.zeros((4,1))
l = 0
for n in range(5,25,5):
    xC = zeri_Cheb(-1, 1, n)
    xE = np.linspace(-1, 1, n+1)
    Le = np.zeros((200,1))
    Lc = np.zeros((200,1))
    for i in range(n+1):
        lagrC = fz.polinomioLagrange(xC, i)
        lagrE = fz.polinomioLagrange(xE, i)
        Le = Le + np.abs(np.polyval(lagrE, xx))
        Lc = Lc + np.abs(np.polyval(lagrC, xx))

    LLe[l] = np.max(Le)
    LLc[l] = np.max(Lc)
    l += 1

print("Costante di Lebesgue con nodi equispaziati ", LLe)
print("Costante di Lebesgue con nodi di Cheb ", LLc)


import numpy as np
from sympy import *
from scipy.optimize import approx_fprime

x = symbols('x')
y = symbols('y')

# first function
f = 2 * x ** 2 * y ** 3 + 1 / x + y ** 2 * x + 7
f1x = diff(f, x)
print(format(f1x))

f1y = diff(f, y)
print(format(f1y))

# second function
f = x ** 2 * y - sin(x * y) + cos(x ** 2) + 6 * y
f1x = diff(f, x)
print(format(f1x))

f1y = diff(f, y)
print(format(f1y))

# gradient of the first function
def func(t):
    return 2 * t[0] ** 2 * t[1] ** 3 + 1 / t[0] + t[1] ** 2 * t[0] + 7

eps = np.sqrt(np.finfo(float).eps)
grad = approx_fprime([1, 2], func, [eps, eps])
print(grad)


import numpy as np

__author__ = "Lei Hu <hulei@pmo.ac.cn>"
__version__ = "v1.0"

# * Remarks on Convolution Theorem
#    NOTE on Symbols: L theoretically can be even / odd, recall w = (L-1)//2 & w' = L//2 and w+w' = L-1
#    a. To make use of FFT with convolution theorem, it is necessary to convert convolution-kernel
#        with relatively small size by Circular-Shift & tail-Zero-padding (CSZ) to align the Image-Size.
#    b. If we have an image elementwise-multiplication FI * FK, where FI and FK are conjugate symmetric,
#        Now we want to get its convolution counterpart in real space, we can calculate the convolution kernel by iCSZ on IDFT(FK).
#        In this process, we have to check the lost weight by size truncation, if the lost weight is basically inappreciable,
#        the equivalent convolution is a good approximation.

class ConvKernel_Convertion:
    def CSZ(ConvKernel, N0, N1):
        L0, L1 = ConvKernel.shape
        w0, w1 = (L0-1) // 2, (L1-1) // 2
        pd0, pd1 = N0 - L0, N1 - L1
        TailZP = np.lib.pad(ConvKernel, ((0, pd0), (0, pd1)), 'constant', constant_values=(0, 0))    # Tail-Zero-Padding
        KIMG_CSZ = np.roll(np.roll(TailZP, -w0, axis=0), -w1, axis=1)    # Circular-Shift
        return KIMG_CSZ

    def iCSZ(KIMG, L0, L1):
        N0, N1 = KIMG.shape
        w0, w1 = (L0-1) // 2, (L1-1) // 2
        KIMG_iCSZ = np.roll(np.roll(KIMG, w1, axis=1), w0, axis=0)    # inverse Circular-Shift
        ConvKernel = KIMG_iCSZ[:L0, :L1]    # Tail-Truncation
        lost_weight = 1.0 - np.sum(np.abs(ConvKernel)) / np.sum(np.abs(KIMG_iCSZ))
        print('MeLOn CheckPoint: Tail-Truncation Lost-Weight [%.4f %s] (Absolute Percentage Error) ' %(lost_weight*100, '%'))
        return ConvKernel


import numpy as np
from numpy import matrix
from numpy import isclose
from numpy import allclose
from numpy.random import random as rand
from numpy.linalg import eig
from numpy.linalg import eigh
from numpy.linalg import det
import math
from math import pi, acos
from math import fabs
from pymatgen.core.operations import SymmOp
from copy import deepcopy
rad = pi/180.
deg = 180./pi

def angle(v1, v2):
    '''
    Calculate the angle (in radians) between two vectors
    '''
    v1 = np.real(v1)
    v2 = np.real(v2)
    dot = np.dot(v1, v2)
    if isclose(dot, 1.0):
        return 0
    elif isclose(dot, -1.0):
        return pi
    return acos(dot / (np.linalg.norm(v1) * np.linalg.norm(v2)))

def random_shear_matrix(width=1.0, unitary=False):
    '''
    Generate a random symmetric shear matrix with Gaussian elements. If unitary
    is True, normalize to determinant 1
    '''
    mat = np.zeros([3,3])
    determinant = 0
    while determinant == 0:
        a, b, c = np.random.normal(scale=width), np.random.normal(scale=width), np.random.normal(scale=width)
        mat = np.array([[1,a,b],[a,1,c],[b,c,1]])
        determinant = np.linalg.det(mat)
    if unitary:
        new = mat / np.cbrt(np.linalg.det(mat))
        return new
    else: return mat

def random_vector(minvec=[0.,0.,0.], maxvec=[1.,1.,1.], width=0.35, unit=False):
    '''
    Generate a random vector for lattice constant generation. The ratios between
    x, y, and z of the returned vector correspond to the ratios between a, b,
    and c. Results in a Gaussian distribution of the natural log of the ratios.
    '''
    vec = np.array([np.exp(np.random.normal(scale=width)), np.exp(np.random.normal(scale=width)), np.exp(np.random.normal(scale=width))])
    if unit:
        return vec/np.linalg.norm(vec)
    else:
        return vec

def is_orthogonal(m, tol=.001):
    #Calculate whether or not a matrix is orthogonal
    m1 = np.dot(m, np.transpose(m))
    m2 = np.dot(np.transpose(m), m)
    if not allclose(m1, np.identity(3), rtol=tol) or not allclose(m2, np.identity(3), rtol=tol):
        return False
    else:
        return True

def aa2matrix(axis, angle, radians=True, random=False):
    '''
    Given an axis and an angle, return a 3x3 rotation matrix
    Based on:
    https://en.wikipedia.org/wiki/Rotation_matrix#Axis_and_angle
    '''
    #Convert to radians if necessary
    if radians is not True:
        angle *= rad
    #Allow for generation of random rotations
    if random is True:
        a = rand()
        axis = [rand(),rand(),rand()]
        angle = rand()*pi*2
    #Ensure axis is a unit vector
    axis = axis / np.linalg.norm(axis)
    #Define quantities which are reused
    x = np.real(axis[0])
    y = np.real(axis[1])
    z = np.real(axis[2])
    c = math.cos(angle)
    s = math.sin(angle)
    C = 1 - c
    #Define the rotation matrix
    Q = np.zeros([3,3])
    Q[0][0] = x*x*C + c
    Q[0][1] = x*y*C - z*s
    Q[0][2] = x*z*C + y*s
    Q[1][0] = y*x*C + z*s
    Q[1][1] = y*y*C + c
    Q[1][2] = y*z*C - x*s
    Q[2][0] = z*x*C - y*s
    Q[2][1] = z*y*C + x*s
    Q[2][2] = z*z*C + c
    return Q

def matrix2aa(m, radians=True):
    '''
    Return the axis and angle from a rotation matrix.
    m must be an orthogonal matrix with determinant 1.
    The axis is an eigenvector with eigenvalue 1.
    The angle is determined by the trace and the asymmetryic part of m.
    Based on:
    https://en.wikipedia.org/wiki/Rotation_matrix#Axis_and_angle
    '''
    if type(m) == SymmOp:
        m = m.rotation_matrix
    #Check if m is the identity matrix
    if allclose(m, np.identity(3)):
        return None, 0.
    if not is_orthogonal(m):
        print("Error: matrix is not orthogonal.")
        return
    #Check that m has posititve determinant
    if not isclose(det(m), 1, rtol=.001):
        print("Error: invalid rotation matrix, determinant is not 1.")
        print("Divide matrix by inversion operation beore calling matrix2aa.")
        return
    #Determine the eigenvector(s) of m
    e = np.linalg.eig(m)
    eigenvalues = e[0]
    possible = np.transpose(e[1])
    eigenvectors = []
    for v in possible:
        if allclose(v, np.dot(m, v)):
            eigenvectors.append(v)
    #Determine the angle of rotation
    if len(eigenvectors) == 1:
        v = eigenvectors[0]
        x = m[2][1] - m[1][2]
        y = m[0][2] - m[2][0]
        z = m[1][0] - m[0][1]
        r = math.sqrt(x**2+y**2+z**2)
        t = m[0][0] + m[1][1] + m[2][2]
        theta = np.arctan2(r, t-1.)
        #Ensure 0<theta<pi
        if theta > pi:
            #Make sure 180 degree rotations are not converted to 0
            if isclose(theta, pi, atol=1e-2, rtol=1e-3):
                theta = pi
            else:
                theta = pi*2 - theta
        if theta < 0:
            theta *= -1
            v *= -1
        #Convert to degrees if necessary
        if radians is not True:
            theta *= deg
        return v, theta
    #If no eigenvectors are found
    elif len(eigenvectors) == 0:
        print("Error: matrix2aa did not find any eigenvectors.")
        return
    #If multiple eigenvectors are found
    elif len(eigenvectors) > 1:
        print("Warning: multiple eigenvectors found.")
        print("Found eigenvectors:")
        print(v)
        return None, 0.

def rotate_vector(v1, v2):
    '''
    Rotates a vector v1 to v2 about an axis perpendicular to both
    Returns the 3x3 rotation matrix used to do so
    '''
    v1 = v1 / np.linalg.norm(v1)
    v2 = v2 / np.linalg.norm(v2)
    dot = np.dot(v1, v2)
    #Handle collinear vectors
    if np.isclose(dot, 1, rtol=.0001):
        return np.identity(3)
    elif np.isclose(dot, -1, rtol=.0001):
        r = [rand(),rand(),rand()]
        v3 = np.cross(v1, r)
        return aa2matrix(v3, pi)
    theta = angle(v1, v2)
    v3 = np.cross(v1, v2)
    return aa2matrix(v3, theta)

def are_equal(op1, op2, allow_pbc=True, rtol=1e-3, atol=1e-3):
    #Check two SymmOps for equivalence
    #pbc=True means integer translations will be ignored
    m1 = op1.rotation_matrix
    m2 = op2.rotation_matrix
    #Check that rotations are equivalent
    if not np.allclose(m1, m2, rtol=rtol, atol=atol):
        return False
    v1 = op1.translation_vector
    v2 = op2.translation_vector
    if allow_pbc is False:
        #Check if translation vectors are equal
        if np.allclose(v1, v2, rtol=rtol, atol=atol):
            return True
        else: return False
    elif allow_pbc is True:
        #Check if translation vectors are equal up to integer difference
        difference = v1 - v2
        if np.allclose(difference, np.round(difference), rtol=rtol, atol=atol):
            return True
        else: return False

class OperationAnalyzer(SymmOp):
    '''
    Class for comparing operations. Stores rotation axis, angle, as well as
    the type of operation (identity, inversion, rotation, or rotoinversion).
    By default, takes a SymmOp as argument.
    Note: rotoinversions with odd-order rotational parts will have an over-all
        even order. For example, the order of (-3) is 6.
    Note: reflections are treated as rotoinversions of order 2.
    '''
    #TODO: include support for off-center operations
    #TODO: include support for shear and scaling operations
    #TODO: include support for matrix-column and axis-angle initialization
    def get_order(angle, rotoinversion=False, tol=1e-2):
        #Find the order of a rotation based on its angle
        found = False
        for n in range(1, 61):
            x = (n*angle) / (2.*pi)
            y = x - np.round(x)
            if abs(y) <= tol:
                found = True
                break
        if found is True:
            #Double order of odd-rotation rotoinversions
            if rotoinversion is True:
                if n % 2 == 1:
                    return int(n * 2)
                else:
                    return int(n)
            else:
                return int(n)
        if not found:
            return "irrational"

    def __init__(self, op):
        if type(op) == deepcopy(SymmOp):
            self.op = op
            self.tol = op.tol
            self.affine_matrix = op.affine_matrix
            self.m = op.rotation_matrix
            self.det = det(self.m)
        elif (type(op) == np.ndarray) or (type(op) == np.matrix):
            if op.shape == (3,3):
                self.op = SymmOp.from_rotation_and_translation(op, [0,0,0])
                self.m = self.op.rotation_matrix
                self.det = det(op)
        else:
            print("Error: OperationAnalyzer requires a SymmOp or 3x3 array.")
        #If rotation matrix is not orthogonal
        if not is_orthogonal(self.m):
            self.type = "general"
            self.axis, self.angle, self.order, self.rotation_order = None, None, None, None
        #If rotation matrix is orthogonal
        else:
            #If determinant is positive
            if det(self.m) > 0:
                self.inverted = False
                self.axis, self.angle = matrix2aa(self.m)
                if isclose(self.angle, 0):
                    self.type = "identity"
                    self.order = int(1)
                    self.rotation_order = int(1)
                else:
                    self.type = "rotation"
                    self.order = OperationAnalyzer.get_order(self.angle)
                    self.rotation_order = self.order
            #If determinant is negative
            elif det(self.m)< 0:
                self.inverted = True
                mi = self.m * -1
                self.axis, self.angle = matrix2aa(mi)
                if isclose(self.angle, 0):
                    self.type = "inversion"
                    self.order = int(2)
                    self.rotation_order = int(1)
                else:
                    self.axis *= -1
                    self.type = "rotoinversion"
                    self.order = OperationAnalyzer.get_order(self.angle, rotoinversion=True)
                    self.rotation_order = OperationAnalyzer.get_order(self.angle, rotoinversion=False)
            elif det(self.m) == 0:
                self.type = "degenerate"
                self.axis, self.angle = None, None
    def __str__(self):
        #Avoid printing '-0.' instead of '0.'
        if self.axis is not None:
            if len(self.axis) == 3:
                for i, x in enumerate(self.axis):
                    if isclose(x, 0):
                        self.axis[i] = 0.
        return ("~~ Operation: "+self.op.as_xyz_string()+" ~~"+
            "\nType: "+str(self.type)+
            "\nOrder: "+str(self.order)+
            "\nAngle: "+str(self.angle)+
            "\nAxis: "+str(np.real(self.axis)) )

    def is_conjugate(self, op2):
        '''
        Returns whether or not another operation is conjugate
        (the same operation in a different reference frame)
        Rotations with the same order will not always return True. For example,
        a 5/12 and 1/12 rotation will not be considered conjugate.
        '''
        if type(op2) != OperationAnalyzer:
            opa2 = OperationAnalyzer(op2)
            if opa2.type == self.type:
                if self.type == "rotation" or self.type == "rotoinversion":
                    ratio = self.angle / opa2.angle
                    if isclose(fabs(ratio), 1., atol=1e-2):
                        return True
                elif self.type == "identity" or self.type == "inversion":
                    return True
            else:
                return False
        else:
            if op2.type == self.type:
                if self.type == "rotation" or self.type == "rotoinversion":
                    ratio = self.angle / op2.angle
                    if isclose(ratio, 1., atol=1e-2):
                        return True
                elif self.type == "identity" or self.type == "inversion":
                    return True
            else:
                return False

    def are_conjugate(op1, op2):
        '''
        Returns whether two operations are conjugate
        '''
        if type(op1) != OperationAnalyzer:
            opa1 = OperationAnalyzer(op1)
        return opa1.is_conjugate(op2)

class orientation():
    '''
    Stores orientations for molecular crystals based on vector constraints.
    Can be stored to regenerate orientations consistent with a given constraint
    vector, without re-calling orientation_in_wyckoff_position.
    args:
        matrix: a 3x3 rotation matrix to initialize with
        degrees: the number of degrees of freedom...
            0: The orientation refers to a single rotation matrix
            1: The orientation can be rotated about a single axis
            2: The orientation can be any pure rotation matrix
    '''

    def __init__(self, matrix, degrees=0, axis=None):
        if (not is_orthogonal(matrix)):
            print("Error: Supplied orientation matrix is not orthogonal")
            return
        if (degrees == 1) and (axis is None):
            print("Error: Constraint vector required for orientation")
        self.matrix = np.array(matrix)
        self.degrees = 0
        self.axis = axis

    def get_matrix(self, angle="random"):
        #Return a SymmOp object rotated by given angle.
        #If "random", rotates by a random amount
        if self.degrees == 2:
            if angle == "random":
                return aa2matrix(1,1,random=True)
            else:
                return self.matrix
        elif self.degrees == 1:
            if angle == "random":
                R = aa2matrix(self.axis, rand()*2*pi)
                return np.dot(R, self.matrix)
            else:
                R = aa2matrix(self.axis, angle)
                return np.dot(R, self.matrix)
        elif self.degrees == 0:
            return self.matrix

    def get_op(self, angle="random"):
        #Return a SymmOp object rotated by given angle.
        #If "random", rotates by a random amount
        m = self.get_matrix(angle=angle)
        return SymmOp.from_rotation_and_translation(m,[0,0,0])

    def from_constraint(v1, c1):
        #c1 is the constraint vector; v1 will be rotated onto it
        m = rotate_vector(v1, c1)
        return orientation(m, degrees=1, axis=c1)

#Test Functionality
if __name__ == "__main__":
#----------------------------------------------------
    '''
    #Check that OperationAnalyzer works
    for string in ['x,y,z','-x,y,z','x,-y,z','x,y,-z','-x,-y,z','-x,y,-z','x,-y,-z','-x,-y,-z']:
        op = SymmOp.from_xyz_string(string)
        opa = OperationAnalyzer(op)
        print(opa)'''

    '''#Check that is_conjugate works
    from structure import random_vector
    for i in range(20):
        a = rand()*2*pi
        op2 = aa2matrix(random_vector(), a)
        print(OperationAnalyzer.are_conjugate(op1, op2))'''

    op = SymmOp.from_rotation_and_translation(aa2matrix([1,0,0], pi/6),[0,0,0])
    ops = [op]
    from pymatgen.symmetry.analyzer import generate_full_symmops
    symm_m = generate_full_symmops(ops, 1e-3)
    for op in symm_m:
        opa = OperationAnalyzer(op)
        print(opa.order)


#%%
# visit https://www.youtube.com/watch?v=ZB7BZMhfPgk for numpy axis explanation: around 1hr 42min
import numpy as np

# create a pseudo number generator
# but if you seed it and use it on different random functions then result will be different
np.random.seed(42)
#%%
# 1D array
a = np.array([1,2,3,4,5], dtype='float')

print(a)
print(a.ndim)

# 2D array sample
# changing the elements
a = np.array([[1,2,3,4,5,6,7],[8,9,10,11,12,13,14]])
a[1][2] = 100
# print(a)

a[:,2] =200
print(a)

# use np.nditer() for iterating through all the values irrespective of the of the shape
for i in np.nditer(a):
    print(i)

#%%
# 2D array
b = np.array([[1,2,3,4,5],[11,12,13,14,15],[16,17,18,19,20]], dtype='int32')

print(b.shape)
print(b.ndim)
print(b.size)
print(b.itemsize)
print(b.nbytes)

#%%
# numpy array of size 3,4,5 where 3 is number of arrays inside one big array and in each array there are 4 rows
# 5 columns
c = np.random.rand(3,4,5)
print(c)
print(c.shape)
print(c.ndim)
print(c.size)
print(c.itemsize)
print(c.nbytes)

# accessing elements
print(c[0,0])

# accessing with the stepsize --> startIndex:endIndex:stepsize
print(c[0,0,0::2])

# changing the elements
c[:,0] = [5,3,5,1,2]

# changing the elements
c[:,:,1] = 10
# print(c)

# changing the elements
c[: , 1] = [12,13,14,15,16]
print(c)

#%%

# Initializing different types of Array

# creating 0 numpy array of certain dimension
z = np.zeros((2,3))
print('\nZeros Array Of {} size'.format(z.shape))
print(z)

# using empty_like
t = np.random.rand(2,3)
z1 = np.empty_like(t)
print(t)
print('\nCreating similar array of size similar to t')
print(z1)

# creating empty array
z1 = np.empty((2,3))
print('\nCreating empty array')
print(z1)

# creating array of 1's also you can use one_like for similar array of 1 of any other array
z1 = np.ones((3,4,2))
print('\nArrays of all one\'s of shape {}'.format(z1.shape))
print(z1)

# if you want to create an array of certain value use np.full
value = 66
z1 = np.full((3,4,1),value)
print('\n Array of shape {} where each element is of value {}'.format(z1.shape, value))
print(z1)

# if you want to craete an array of certain value of any other given array size
t = np.random.rand(2,3)
value = 12
z1 = np.full_like(t, value)
print('\n Array of shape {} which is similar to t with all the values {}'.format(t.shape, value))
print(z1)

# create an array of given size with uniform distribution for normal distribution use randn
z1 = np.random.rand(3,4,4)
print('\n Random array of shape {} with all decimal numbers between 0 to 1'.format(z1.shape))
print(z1)

# generate an array of number between certain value only integers
low = 1
high = 100
z1 = np.random.randint(low, high, size=(3,4))
print('\n Random Array between {} and {} of shape {}'.format(low,high, z1.shape))
print(z1)

# generate an array of number between certain values where float is also allowed uniform distribution
# can be use
low = 1
high = 100
z1 = np.random.uniform(low, high, size= (3,4))
print('\n Random Array following Uniform distribution between {} and {} of shape {}'.format(low, high, z1.shape))
print(z1)

# generating an identity matrix
z1 = np.identity(5)
print('\n Identity Matrix of shape {}'.format(z1.shape))
print(z1)

# be careful when you copy array
a = np.array([1,2,3,4])
b = a.copy() # to create a deep copy hence a and b are independent of each other changes

#%%
# create an array with mentioned diaognal numbers
a = np.diag([1,2,3,4,5])
print(a)

# extracting the diognam numbers
a = np.arange(25).reshape(5,5)
print(a)
print(np.diag(a))

#%%
# mathematics operation
a = np.random.randint(1, 100, size=(3,6))
b = np.random.randint(1,100, size=(3,6))

print('\nArray A ')
print(a)
print('\nArray B')
print(b)

# addition
addition = a + b
print('\nAddition')
print(addition)

# multiplication --> similarly division and subtraction can be performed
# Note: the operations are element wise
multiplication = a * b
print('\nMultiplication')
print(multiplication)

# cosine --> input is considered in radians
cosine = np.cos(a)
print('\n Cosine of A')
print(cosine)

# power
value = 3
power = a**value
print('\nPower of {}'.format(value))
print(power)

#%%
# Linear Algebra

# for matrix multiplication use matmul
a = np.random.randint(1,10, size=(2,3))
b = np.random.randint(1, 10, size=(3,2))

print('\nArray A ')
print(a)
print('\nArray B')
print(b)

# matrix multiplication
matmul = np.matmul(a,b)
print('\nMatrix multiplication of A and B')
print(matmul)

# getting the determinant of matrix
a = np.random.randint(1, 10, size=(3,3))
det = np.linalg.det(a)
print('\nDeterminant using np.linalg.det(a)')
print(det)

# inverse of a square matrix
a = np.random.randint(1, 10, size=(3,3))
print('\nMatrix A')
print(a)
inverse = np.linalg.inv(a)
print('Inverse of Matrix A')
print(inverse)

# transpose of a matrix
a = np.random.randint(1, 10, size=(3,8))
print('\nMatrix A')
print(a)
transpose = np.transpose(a)
print('Transpose of Matrix A')
print(transpose)

#%%

# performing statistics in numpy
np.random.seed(42)
a = np.random.randint(1,10, size=(2, 3, 4, 5))
print('\nArray A')
print(a)

# min
a_min = np.min(a)
print('\nMinimum in A in whole A')
print(a_min)

a_min = np.min(a, axis=0)
print('\nMinimum in A at axis 0')
print(a_min)

print('sum around axis = 0')
print(np.sum(a, axis=2))

#%%

# reorganizing array
a = np.random.randint(1, 10, size=(3,3,3))
print('\nBefore Organizing')
print(a)

b = a.reshape((9,3))
print('\nAfter Organizing')
print(b)

# stacking arrays vertically
a= np.random.randint(1,10, size=(3,3))
b = np.random.randint(1, 10, size= (4,3))

print('\nArray A ')
print(a)
print('\nArray B')
print(b)

vertical_stack = np.vstack([a,b])
print('\nVertical Stack')
print(vertical_stack)

# horizontal stack
a= np.random.randint(1,10, size=(3,8))
b = np.random.randint(1, 10, size= (3,7))

print('\nArray A ')
print(a)
print('\nArray B')
print(b)
horizontal_stack = np.hstack([a,b])
print('\nHorizontal Stack')
print(horizontal_stack)

#%%

# converting a 1d array to 2d array
# if you have 1d array and u want to change to 2d array you cannot do it with transpose
# np.newaxis add axis at designated level you want

a = np.arange(0,40,10)
a = a[:, np.newaxis] # converting to 2d array
print(a)

a = np.arange(0,10).reshape(2,5)
a = a[:,:, np.newaxis]
print(a.shape)
print(a)
#%%

# if you want to flatten the array, i.e. convert nd array to 1 dimensional array
a = np.arange(25).reshape(5,5)
print(a.ravel())
#%%

# sorting
#sorting with fancy indexing
a = np.array([4, 3, 1, 2])
j = np.argsort(a)
print(j)

print(a[j])

#Sorting along an axis:
a = np.array([[5, 4, 6], [2, 3, 2]])
b = np.argsort(a, axis=0) # try with axis=1
print(b)
#%%
# creating a tile
a = np.tile(np.arange(0,40,10),(3,2))

print(a)

# getting transpose
a = a.T
print(a)

#%%
# working with range:
np_range = np.arange(0,40,5) # 0 to 10 with step size of 5
print(np_range)

# fancy indexing
indices = [1,2,-3] # index position from which other element can be taken out
y = np_range[indices]
print(y)

# manual creation of mask
mask = np.array([0,1,1,1,0,0,1,1], dtype='bool')
print(mask.size)
print(np_range[mask])

# fancy indexing in 2-D
np.random.seed(42)
two_d = np.random.randint(1,100, size=(5,5)) # 5*5 array
print('\nArray 2-D')
print(two_d)

print('\nIndex from row 3 till last and columns 0,2,4')
print(two_d[3:,[0,2,4]])

print('Indexing 0,2,4 row and 4,3 columns')
print(two_d[[0,2,4],[0,4,3]])

#%%
# reshaping data for arange
a = np.arange(25).reshape(5,5)
print('\n (0,24) 5*5 dimension')
print(a)

#%%

a = np.arange(12).reshape(4,3)
b = np.arange(3).reshape(1,3)
print(a)
print(b)
print(a+b)

#%%
# getting the coordinates of the maximum value
np.random.seed(42)
a = np.random.randint(1,1000, size=(2,2, 4, 5))
print('\nArray A')
print(a)

# getting the coordinate of the maximum value in a
print('\nMaximum Values')
print(np.argmax(a, axis=0))
print('\nMaximum Values Coordinate')
print(np.where(a == np.max(a)))

#%%

# numpy reading file using loadtxt()
'''
We use loadtxt() when we have data of same type
deleimiter -- default is white space
skiprows - to the skip the n number of rows
usecols - to use the columns
'''

dataset = np.loadtxt('numpytutorial/scratch.txt', dtype=float)

print(dataset)
#%%

# genfromtxt() for reading datafile where data columns can be of different rows
# data = np.genfromtxt('titanic.csv', delimiter=',', names=True, dtype=None) menton dtype as None for autoassin of type
# genfromtxt is a 1d array not a 2-d array. a little misleadiing

dataset = np.genfromtxt('numpytutorial/winter.csv', delimiter=',', dtype=None,
                        names=['labels','Year','City','Sport','Discipline','Athlete','Country','Gender','Event','Medal'],
                        skip_header=1)

print(dataset[[1,3,4,5,6]]['Sport'])
print(dataset[1:6]['Sport'])

# if you want a certain column use dataset[<column-name>]

# in pandas .values return the numpy array
import pandas as pd
dataset = pd.read_csv('numpytutorial/winter.csv')
dataset = dataset.values
print(type(dataset))

#%%
#TODO: Delete code below this line

import matplotlib.pyplot as plt
import pandas as pd
help(plt.legend)
#%%

def sum_num(*args):
    print(*args)

args = [1,2,3,4,5]
kwargs = {'name':1,'age':2,'system':3}
print(len(args))

#%%
# computing the percentile
import numpy as np

data = np.random.binomial(9,0.25,size=1000)
print(np.sum(data==9))
import matplotlib.pyplot as plt
plt.hist(data, cumulative=True, density=True, histtype='step', bins=100)
plt.show()


"""This module plots the first basis function for degree 0, 1, 2, 3, 4, 5.

Example:
> cd ~/sibl/geo/doc
> conda activate siblenv
> python plot_bspline_N0_p0_to_p4.py
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
from matplotlib.ticker import MultipleLocator

import ptg.bspline as bsp

# degree = 0
display = True
dpi = 100  # dots per inch
latex = True  # False
n_bisections = 4  # 2^n_bisections
serialize = True  # False

colors = ("tab:blue", "tab:orange", "tab:green", "tab:red", "tab:purple", "tab:cyan")
linestyles = ("solid", "dashed", "dashdot")

if latex:
    rc("font", **{"family": "serif", "serif": ["Computer Modern Roman"]})
    rc("text", usetex=True)

degrees = (0, 1, 2, 3, 4)  # constant, linear, quadratic, etc.
knot_vectors = (
    (0, 1),
    (0, 0, 1, 2, 2),
    (0, 0, 0, 1, 2, 3, 3, 3),
    (0, 0, 0, 0, 1, 2, 3, 4, 4, 4, 4),
    (0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 5, 5, 5, 5),
)
coefs = (
    (1.0,),
    (0.0, 1.0, 0.0),
    (0.0, 0.0, 1.0, 0.0, 0.0),
    (0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0),
    (0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0),
)
labels = ("$N_0^0$", "$N_0^1$", "$N_0^2$", "$N_0^3$", "$N_0^4$")

# fig = plt.figure(figsize=plt.figaspect(1.0 / (len(knot_vector) - 1)), dpi=dpi)
fig = plt.figure(figsize=plt.figaspect(1.0 / max(knot_vectors[-1])), dpi=dpi)
ax = fig.gca()
# ax.grid()
ax.grid(True, which="major", linestyle="-")
ax.grid(True, which="minor", linestyle=":")

for i in np.arange(len(degrees)):

    knot_vector = knot_vectors[i]
    coef = coefs[i]
    degree = degrees[i]
    label = labels[i]

    N0_p = bsp.Curve(knot_vector, coef, degree)
    result = N0_p.is_valid()

    npts = int((knot_vector[-1] - knot_vector[0]) * (2 ** n_bisections) + 1)
    # tmin, tmax, npts = knot_vector[0], knot_vector[-1], 13
    tmin, tmax, npts = knot_vector[0], knot_vector[-1], npts

    t = np.linspace(tmin, tmax, npts, endpoint=True)
    y = N0_p.evaluate(t)
    ax.plot(
        t,
        y,
        "-",
        linewidth=2,
        label=label,
        linestyle=linestyles[np.remainder(i, len(linestyles))],
    )

ax.set_xlabel(r"$t$")
ax.set_ylabel(r"$N^{p\in[0,4]}_0(t)$")
ax.legend(loc="upper right")

_eps = 0.1
ax.set_xlim([knot_vectors[-1][0] - 2 * _eps, knot_vectors[-1][-1] + 2 * _eps])
ax.set_ylim([0.0 - 2 * _eps, 1.0 + 2 * _eps])

ax.set_aspect("equal")

ax.xaxis.set_major_locator(MultipleLocator(1.0))
ax.xaxis.set_minor_locator(MultipleLocator(0.25))
ax.yaxis.set_major_locator(MultipleLocator(1.0))
ax.yaxis.set_minor_locator(MultipleLocator(0.25))

if display:
    plt.show()

if serialize:
    extension = ".pdf"  # or '.svg'
    bstring = "N_0_p=0to4" + extension
    # fig.savefig(bstring, bbox_inches="tight")
    fig.savefig(bstring, bbox_inches="tight", pad_inches=0)


#
# Running mean and variance with supposedly superior numerical
# stability.
#
# Code based on the explanation and C++ code from this website:
# http://www.johndcook.com/standard_deviation.html
#
# This better way of computing variance goes back to a 1962 paper by
# B. P. Welford and is presented in Donald Knuth's Art of Computer
# Programming, Vol 2, page 232, 3rd edition.
#
#
# Author: Advait Jain

import numpy as np
import copy

class Running_Mean_And_Variance():
    def __init__(self):
        self.mn = None
        self.s = 0
        self.k = 0

    def update(self, x):
        self.k += 1
        if self.k == 1:
            self.mn = x
            self.s = 0.
        else:
            mn_old = self.mn
            self.mn = mn_old + (x - mn_old) / self.k
            self.s = self.s + (x - mn_old) * (x - self.mn)

    def mean(self):
        return copy.copy(self.mn)

    def std(self):
        return np.sqrt(self.variance())

    def variance(self):
        return copy.copy(self.s/(self.k-1))

    def biased_std(self):
        return np.sqrt(self.biased_variance())

    def biased_variance(self):
        return copy.copy(self.s/(self.k))

if __name__ == '__main__':
    a = np.random.randn(4, 100)

    print 'Mean and Std computed using numpy:'
    print 'Mean:', np.mean(a, 1)
    print 'Std:', np.std(a, 1)

    print ''
    print 'Running Mean and Std:'

    rmav = Running_Mean_And_Variance()
    for b in a.T:
        rmav.update(b)

    print 'Mean:', rmav.mean()
    print 'Unbiased Std:', rmav.std()
    print 'Biased Std:', rmav.biased_std()


#ZADANIE 1
#Wykorzystaj metode iteracyjna Jacobiego do
#znalezienia rozwiazan ukladow rownan a) – d). Wykonaj
#obliczenia w przy uzyciu arkusza kalkulacyjnego oraz programu
#napisanego w Python.

import numpy as np;

def diag_dom_test(a):
    test = []
    for i in range(len(a)):
        s = sum([np.abs(a[i,j]) for j in range(len(a)) if i!=j])
        if np.abs(a[i,i])>=s:
            test.append(1)
        else:
            test.append(0)
    return test

def Jacobi_iter(a,b,max_iter):
    x = np.zeros_like(b)
    for k in range(max_iter):
        x_k = np.zeros_like(x)
        for i in range(a.shape[0]):
            s = sum([a[i,j]*x[j] for j in range(a.shape[0]) if i != j])
            x_k[i] = (b[i]-s)/a[i,i]
        if np.allclose(x, x_k, rtol=1e-8):
            break
        x=np.copy(x_k)
    print("Rozwiazanie:")
    print(x)
    error = np.dot(a, x) - b
    print("Blad:")
    print(error)
    print("Liczba iteracji:")
    print(k)

a1 = np.array([[3.0, 1.0, -1.0], [1.0, 2.0, -1.0], [1.0, 1.0, 5.0]])
b1 = np.array([181.05, 108.35, 142.55])
print(30*'-')
print("Przyklad A\n")
print("Sprawdzenie czy macierz a jest diagonalnie dominujaca. Jesli zwrocony zostal wektor jedynek to jest diagonalnie dominujaca.")
print(diag_dom_test(a1))
print("Jesli macierz a nie jest diagonalnie dominujaca to wyniki sa niepoprawne.\n")
Jacobi_iter(a1, b1, 100)
print(30*'-')

a2 = np.array([[1.0, -1.0, 2.0], [3.0, 2.0, 1.0], [2.0, -3.0, -2.0]])
b2 = np.array([5.0, 10.0, -10.0])
print(30*'-')
print("Przyklad B\n")
print("Sprawdzenie czy macierz a jest diagonalnie dominujaca. Jesli zwrocony zostal wektor jedynek to jest diagonalnie dominujaca.")
print(diag_dom_test(a2))
print("Jesli macierz a nie jest diagonalnie dominujaca to wyniki sa niepoprawne.\n")
Jacobi_iter(a2, b2, 100)
print(30*'-')

a3 = np.array([[5.0, 1.0, 1.0, 1.0], [2.0, -1.0, -1.0, 1.0], [3.0, -1.0, 2.0, -2.0], [5.0, -4.0, 3.0, -2.0]])
b3 = np.array([685.0, 165.0, 256.0, 361.0])
print("Przyklad C\n")
print("Sprawdzenie czy macierz a jest diagonalnie dominujaca. Jesli zwrocony zostal wektor jedynek to jest diagonalnie dominujaca.")
print(diag_dom_test(a3))
print("Jesli macierz a nie jest diagonalnie dominujaca to wyniki sa niepoprawne.\n")
Jacobi_iter(a3, b3, 100)
print(30*'-')

a4 = np.array([[1.0, 3.0, 5.0], [2.0, 5.0, 1.0], [2.0, 3.0, 8.0]])
b4 = np.array([10.0, 8.0, 3.0])
print("Przyklad D\n")
print("Sprawdzenie czy macierz a jest diagonalnie dominujaca. Jesli zwrocony zostal wektor jedynek to jest diagonalnie dominujaca.")
print(diag_dom_test(a4))
print("Jesli macierz a nie jest diagonalnie dominujaca to wyniki sa niepoprawne.\n")
Jacobi_iter(a4, b4, 100)
print(30*'-')

#Metoda iteracyjna Jacobiego moze byc uzyta tylko wtedy gdy:
#- macierz a jest diagonalnie dominujaca
#- lub gdy macierz a jest macierza symetryczna
#- lub gdy macierz a jest dodatnio okreslona
#Tylko pierwsza z macierzy a podanych w przykladach powyzej jest diagonalnie dominujaca.
#Pozostale trzy macierze a nie sa diagonalnie dominujace.
#Wyniki jakie zwraca metoda iteracyjna Jacobiego dla ostatnich trzech przykladow nie sa poprawne.


import numpy as np
import pandas as pd

def parallel_analysis(data, n=100, alpha=0.05, centered=True,
                      method='normal', output_eig=False):

  """
  This function determines the number of meaningful principal
  components.

  Parameters
  ----------
  data : array_like
    data matrix (row: observations, columns: variables)
  n : integer, optional
    number of random/randomized matrices. Default is 100.
  alpha : float, optional
    significance level. Default is 0.05.
  centered : bool, optional
    If True, centering is performend. Default is True.
  method : {'normal', 'shuffle', 'boot', }, optional
    Method for generating uncorrelated matrices. 'normal' is
    sampling from N(0,sigma_i), where sigma_i is the standard
    deviation of the ith variable in the original data.
    'shuffle' is  sampling from the original data without
    replacement. 'boot' is sampling with replacement. Default
    is 'normal'.
  output_eig : bool, optional
    If True, eigenvalues are returned. Default is False.

  Returns
  -------
  n_component : integer
    number of meaningful principal components
  eig_orig : ndarray
    eigenvalues of original data
  eig_rand : ndarray
    eigenvalues of random data

  """
  X = np.asarray(data)
  n_row, n_col = X.shape

  # original data
  def calc_eig(Z):
    Z2    = Z - Z.mean(axis=0) if centered else Z
    s_arr = np.linalg.svd(Z2, compute_uv=False)
    return s_arr**2 / (Z.shape[0] - 1)
  eig_orig = calc_eig(X)

  # generate random/randomized matrices
  col_arr  = np.ones_like(X).astype(int) * np.arange(n_col)
  if method == 'normal':
    eig_rand = [calc_eig(np.random.randn(*X.shape) * X.std(axis=0, ddof=1))
                for _ in range(n)]
  elif method == 'shuffle':
    eig_rand = [calc_eig(X[pd.DataFrame(np.random.randn(*X.shape)).\
                           rank().astype(int) -1, col_arr]) \
                for _ in range(n)]
  elif method == 'boot':
    eig_rand = [calc_eig(X[np.random.randint(n_row, size=X.shape), col_arr])
                for _ in range(n)]
  else:
    raise ValueError("<method> must be 'normal', 'shuffle', or 'boot'.")
  eig_rand = np.array(eig_rand)

  # comparison
  eig_diff = eig_orig - np.percentile(eig_rand, 100 * (1 - alpha), axis=0)
  idx_arr  = np.where(eig_diff < 0)[0]
  n_comp = idx_arr[0] if len(idx_arr) > 0 else len(eig_diff)

  if output_eig:
    return n_comp, eig_orig, eig_rand
  else:
    return n_comp

if __name__ == '__main__':
  x = random.randn(5, 500)
  n_comp, eig_orig, eig_rand = parallel_analysis(x, output_eig=True,
                                                 method='normal')
  fig, ax = plt.subplots()
  ax.plot(eig_orig[:-1], lw=5)
  ax.plot(np.percentile(eig_rand[:,:-1], 95, axis=0))
  ax.plot(np.percentile(eig_rand[:,:-1], 5, axis=0))
  fig.show()
  print(n_comp)


import numpy as np
import matplotlib.pyplot as plt

EPS = 1e-10

def angle(u, v):
    # theta = arctan(v/u), but the output should be [0, 2*pi]
    r = np.sqrt(u**2+v**2)
    theta = np.arccos(u/(r+EPS)) # [0, pi]
    theta = theta*(v>=0) + (2*np.pi-theta)*(v < 0)  # [0, 2*pi]
    return theta

def lamb_oseen(x, y, Gamma=2e3, rc=40):
    r = np.sqrt(x**2+y**2)+1e-9
    theta = angle(x, y)

    V = Gamma*(1-np.exp(-r**2/rc**2))/(2*np.pi*r)  # circumferential vel
    d_theta = V/r

    u = -V*y/r
    v = V*x/r

    x1 = r*np.cos(theta-d_theta/2)
    y1 = r*np.sin(theta-d_theta/2)
    x2 = r*np.cos(theta+d_theta/2)
    y2 = r*np.sin(theta+d_theta/2)

    return u, v, x1, y1, x2, y2

def sin_flow(x, y, a=6, b=128, scale=5):
    theta = np.arctan(a*np.cos(2*np.pi*x/b)*2*np.pi/b)
    u = scale*np.cos(theta)
    v = scale*np.sin(theta)

    x1, y1 = x, y
    x2, y2 = x, y
    N = 1000
    ds = 0.5*scale/N
    for i in range(N):
        alpha1 = np.arctan(a*np.cos(2*np.pi*x1/b)*2*np.pi/b)
        x1 = x1 - ds*np.cos(alpha1)
        y1 = y1 - ds*np.sin(alpha1)

        alpha2 = np.arctan(a*np.cos(2*np.pi*x2/b)*2*np.pi/b)
        x2 = x2 + ds*np.cos(alpha2)
        y2 = y2 + ds*np.sin(alpha2)

    return u, v, x1, y1, x2, y2

def test_flow():
    x = np.random.rand(100)*128
    y = np.random.rand(100)*128

#     u, v, x1, y1, x2, y2 = lamb_oseen(x, y, Gamma=5e3, rc=20)
    u, v, x1, y1, x2, y2 = sin_flow(x, y, a=20, b=128, scale=25)

    plt.figure(figsize=(10,10))
    plt.scatter(x,y,color='k', linewidth=8)
    plt.scatter(x1,y1,color='r')
    plt.scatter(x2,y2,color='b')
    plt.axis('equal')
    plt.quiver(x1, y1, x2-x1, y2-y1, scale=1, color=(0.5,0.5,0.5), units='xy')
    plt.quiver(x, y, u/2, v/2, scale=1, color=(0.0,0.0,0.5), units='xy')
    plt.show()

if __name__=='__main__':
    test_flow()


def trapezoid(y_vals, h):
  # y_vals: data of isocontours, e.g. areas of isocontours, see Table 4.3 for example
  # h: difference between isocontours, regular space, see Table 4.3 for example
  import numpy as np
  # simplification: V = h/2*(a1+an) + h/2(2*a2+2*a3+2*an-1) as V = first_term + second_term
  first_term = 0.5 * h * (y_vals[0] + y_vals[-1])

  # delete a0 and an
  y_trapez = np.delete(y_vals, 0)
  y_trapez = np.delete(y_trapez, -1)

  second_term = np.sum(2 * y_trapez)
  second_term = 0.5 * h * second_term
  return first_term + second_term


import numpy as np

def decompLU(A):
	L = np.eye(np.shape(A)[0]) #matriz L com a dimensao de A com a diagonal principla igual a 1
	U = np.zeros(np.shape(A)) #matriz U full zero
	lim = np.shape(A)[1] #pegando o tamanho da coluna de A (matriz quadrada)
	sum = 0

	for i in range(0, lim):
		for j in range(0, lim):
			sum = 0
			#Parte do somatorio de U que vai de 0 a i-1 e acumula L[i][k]*U[k][j] sendo k a variavel iteradora
			for k in range (0, i):
				sum += L[i,k]*U[k,j]
			U[i,j] = A[i,j] - sum #atribuindo o valor de U[i][j]

		for j in range (0, lim):
			sum = 0
			#Parte do somatorio de L que vai de 0 a i-1 e acumula L[j][k]*U[k][i] sendo k a variavel iteradora
			for k in range (0, i):
				sum+= L[j,k]*U[k,i]
			L[j,i] = (A[j,i] - sum)/U[i,i] #atribuindo o valor L[i][j]

	return L, U #retornando o resultado

def solveLU(L, U, b):
	#Resolvendo o sistema A*x = b
	#Como A = L*U, entao (L*U)*x = b
	#Vamos fazer L*(U*x) = b e U*x=y, portanto L*y=b
	#Logo, para encontrarmos o resultado resolvemos o sistema U*x = y

	y = np.zeros(np.shape(b)) #matriz coluna y com a mesma dimensao de b
	x = np.zeros(np.shape(b)) #matriz coluna x (resultado do sistema) com a mesma dimensao de b

	for i in range (0, np.shape(b)[0]): #resolvendo o sistema  L*y=b
		for j in range (0,i):
			y[i] -= y[j]*L[i,j]
		y[i]+=b[i]

	for i in range (np.shape(b)[0]-1, -1, -1): #resolvendo o sistema U*x=b
		for j in range (np.shape(b)[0]-1, i, -1):
			x[i] -= x[j]*U[i,j]
		x[i]+=y[i]
		x[i]/=U[i,i]

	return x #retornando o resultado

def LeastSquares(x, y, n):
	V = np.vander(x,n) #criando a matriz de vandermonde
	A = np.transpose(V).dot(V) #fazendo os produtos vetoriais e criando a matriz A
	b = np.transpose(V).dot(y) #fazendo os produtos vetoriais e criando a matriz b
	(L,U) = decompLU(A) #fazendo a decomposicao LU para resolver o sistema
	alpha = solveLU(L,U,b) #resolvendo o sistema com a decomposicao LU
	return alpha[::-1] #retornando o vetor ao contrario

x = [-1, 0, 1, 2];
y = [0, -1, 0, 7];
print(x)
print(y)

alpha = LeastSquares(x,y,2+1) #Como queremos um polinomio do segundo grau entao eh igual a n=2+1

print(alpha)


#%%
"""
Created on July 05  2021
Annuity mortgage- payment profile

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak and Emanuele Casamassima
"""
import numpy as np
import matplotlib.pyplot as plt

def Annuity(rate,notional,periods,CPR):
    # it returns a matrix M such that
    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_quote(t)  installment(t)]
    # WARNING! here "rate" and "periods" are quite general, the choice of getting year/month/day.. steps, depends on the rate
    # that the function receives. So, it is necessary to pass the correct rate to the function
    M = np.zeros((periods + 1,6))
    M[:,0] = np.arange(periods + 1) # we define the times
    M[0,1] = notional
    for t in range(1,periods + 1):
        # we are computing the installment at time t knowing the oustanding at time (t-1)
        remaining_periods = periods - (t - 1)

        # Installment, C(t_i)
        M[t,5] = rate * M[t-1,1]/(1 - 1/(1 + rate)**remaining_periods)

        # Interest rate payment, I(t_i) = K * N(t_{i})
        M[t,4] = rate * M[t-1,1]

        # Notional payment, Q(t_i) = C(t_i) - I(t_i)
        M[t,3] = M[t,5] - M[t,4]

        # Prepayment, P(t_i)= Lambda * (N(t_i) -Q(t_i))
        M[t,2] = CPR * (M[t-1,1] - M[t,3])

        # notional, N(t_{i+1}) = N(t_{i}) - lambda * (Q(t_{i} + P(t_i)))
        M[t,1] = M[t-1,1] - M[t,3] - M[t,2]
    return M

def mainCode():

    # Initial notional
    N0     = 1000000

    # Interest rates from a bank
    r = 0.05

    # Prepayment rate, 0.1 = 10%
    Lambda = 0.1

    # For simplicity we assume 1 as unit (yearly payments of mortgage)
    T_end = 30
    M = Annuity(r,N0,T_end,Lambda)

    for i in range(0,T_end+1):
        print("Ti={0}, Notional={1:.0f}, Prepayment={2:.0f}, Notional Repayment={3:.0f}, Interest Rate={4:.0f}, Installment={5:.0f} ".format(M[i,0],M[i,1],M[i,2],M[i,3],M[i,4],M[i,5]))

    plt.figure(1)
    plt.plot(M[:,0],M[:,1],'.r')
    plt.grid()
    plt.xlabel('time')
    plt.ylabel('notional')
    return 0.0

mainCode()

Lecture 08-Mortgages and Prepayments/Materials/AnnuityMortgage.py


#!/usr/bin/env python3
"""
Helper functions for Fourier transform algorithms
"""
# Standard libraries
import numpy as np

def twiddle_factor(k,N, type='exp'):
    """
    Return twiddle factors.
    """
    if type=='cos':
        twiddle_factor = np.cos(2*np.pi*k/N)
    elif type=='sin':
        twiddle_factor = np.sin(2*np.pi*k/N)
    elif type=='exp':
        twiddle_factor = np.exp(2j*np.pi*k/N)
    return twiddle_factor

def normalize(weights, platform):
    """
    Normalize the weights for computing the 1-D FT
    """
    if platform in ("numpy"):
        weights /= weights.size
    if platform == "loihi":
        correction_coef = 127 / weights.max()
        weights = np.ceil(weights * 127 - 0.5)*2
    if platform == "brian":
        weights = weights.T
    return weights

def dft_connection_matrix(nsamples, platform):
    """
    Calculate network weights based on Fourier transform equation

    Parameters:
        nsamples (int): Number of samples in a chirp
        platform (str [loihi|traditional]): If "loihi", values are normalized
         between the limits imposed by the chip; Re-scale weights to the range
         admitted by Loihi (8-bit even values -257 to 254). If "traditional",
         each weight is divided by the total length of the chirp, as in a
         conventional Fourier transform
    Returns:
        real_weight_norm: weights for the connections to the "real" compartments
        imag_weight_norm: weights for the connections to the "imag" compartments
    """
    c = 2 * np.pi/nsamples
    n = np.arange(nsamples).reshape(nsamples, 1)
    k = np.arange(nsamples).reshape(1, nsamples)
    trig_factors = np.dot(n, k) * c
    real_weights = np.cos(trig_factors)
    imag_weights = -np.sin(trig_factors)
    # Normalize the weights based on the used platform
    real_weights_norm = normalize(real_weights, platform)
    imag_weights_norm = normalize(imag_weights, platform)
    return (real_weights_norm, imag_weights_norm)

def fft_connection_matrix(layer, nsamples, platform):
    """
    Connection matrix for a radix-4 fft
    """
    radix = 4
    n_layers = int(np.log(nsamples)/np.log(radix)) # number of layers
    n_bfs = int(radix**n_layers/radix) # number of butterflies
    n_blocks = radix**(layer) # number of blocks
    n_bfs_per_block = int(n_bfs/n_blocks) # number of butterflies in one block
    distance_between_datapoints = radix**(n_layers-layer-1)
    distance_between_blocks = radix**(layer)

    n = np.tile(np.arange(0,radix**(n_layers-layer-1)),radix**(layer+1))
    c = np.tile(np.repeat(np.arange(0,4**(layer+1),4**layer),4**(n_layers-layer-1)),4**layer)

      # radix4 butterfly
    W = np.array([
        [1, 1,   1,  1],
        [1, -1j, -1, 1j],
        [1, -1,  1,  -1],
        [1, 1j,  -1, -1j]
    ])

      # build 4 matrices of radix4 butterfly
    W_rr = np.real(W) # real input real weights real ouput
    W_ir = -1*np.imag(W) # imag input imag weights real output
    W_ri = np.imag(W) # real input imag weights imag ouput
    W_ii = np.real(W) # imag input real weights imag output

    # Fancy matrix structure
    G = np.eye(4**layer, 4**layer)
    B = np.eye(int(nsamples/4**(layer+1)), int(nsamples/4**(layer+1)))

    # Kronecker magic
    M_rr = np.kron(G, np.kron(W_rr, B))
    M_ir = np.kron(G, np.kron(W_ir, B))
    M_ri = np.kron(G, np.kron(W_ri, B))
    M_ii = np.kron(G, np.kron(W_ii, B))

    # Stacking 4 matrices to 2x2
    M = np.vstack([np.hstack([M_rr, M_ir]), np.hstack([M_ri, M_ii])])

    # twiddle factor as 2x2 matrix
    tf_real = np.vstack([np.diag(twiddle_factor(n*c, nsamples, 'cos')),
                         -np.diag(twiddle_factor(n*c,nsamples,'sin'))])
    tf_imag = np.vstack([np.diag(twiddle_factor(n*c, nsamples, 'sin')),
                         np.diag(twiddle_factor(n*c,nsamples,'cos'))])
    tf = np.hstack([tf_real, tf_imag])

    # Twiddle factors times butterfly matrix
    M = np.matmul(tf, M)

    return M

def bit_reverse(data, base, nlayers):
  '''
  Change order of input array by reversing bit order.

  Parameters:
      data (array): 1D data array
      base (int): base = 4 for radix4
      nlayers (int): number of layers
  '''

  ordered_res = data.copy()
  for i in range(len(data)):
    base_repr = np.base_repr(i, base=base, padding=0)[::-1]
    base_repr += (nlayers-len(base_repr))*'0'
    idx = int(base_repr,base)
    ordered_res[idx] = data[i]
  return ordered_res


import numpy as np

def slope_and_intercept(point1, point2):
    m = (point1[1] - point2[1]) * 1.0 / (point1[0] - point2[0])
    c = point1[1] - m * point1[0]
    return m, c

def find_intersection(m1, c1, m2, c2):
    x = (c2 - c1) * 1.0 / (m1 - m2)
    y = m1 * x + c1
    return np.array([x, y])

def find_polar(point, origin):
    diff = point - origin
    r = np.sqrt(np.sum(np.square(diff)))
    theta = np.arctan2(diff[1], diff[0])
    return r, theta

def find_cartesian(r, theta, origin):
    point = np.array([r * np.cos(theta), r * np.sin(theta)])
    return point + origin

def transform(r, theta, delta_theta):
    return r, theta + delta_theta

def pan(points, delta_theta=0.087):
    # Take Note: points are not homogenous, np.array([x, y]) form
    x0, y0 = points[0]  # p0 - TL
    x1, y1 = points[1]  # p1 - TR
    x2, y2 = points[2]  # p2 - BR
    x3, y3 = points[3]  # p3 - BL

    # Find L1
    m1, c1 = slope_and_intercept(points[0], points[3])

    # Find L2
    m2, c2 = slope_and_intercept(points[1], points[2])

    C = find_intersection(m1, c1, m2, c2)

    new_points = []
    for point in points:
        r, theta = find_polar(point, C)
        r, theta = transform(r, theta, delta_theta)
        x, y = find_cartesian(r, theta, C)
        new_points.append([x, y])

    return new_points

# if __name__ == '__main__':
#     print(pan([np.array([5, 8.66]), np.array([8.66, 5]),
#                np.array([4.33, 2.5]), np.array([2.5, 4.33])], 0.087))


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

#Inverse filtering
I = Image.open('....\data_src\img_src\Campus.tif')
width, height = I.size

img = np.array(I, dtype=np.uint8)

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

#Applying steps for filtering in frequency domain:
M, N = np.shape(img) #Sizes of original image

img = np.pad(img, ((0, M), (0, N)), mode='constant', constant_values=0)
P, Q = np.shape(img)#Sizes of padded image

FT = ft.fft2(img)
FT = ft.fftshift(FT)

ply.imshow(np.log(np.sqrt(FT.real**2 + FT.imag**2)), cmap='Greys_r')
ply.colorbar()
ply.title('Fourier transform')
ply.show()

H = np.ones((P, Q), dtype = float)
k = 0.0025 #atmospheric disturbance

for v in range(0, M):
  for u in range(0, N):
    H[M - v, N - u] = np.exp(-k*(u**2 + v**2)**(5/6))
    H[M - v, N + u] = np.exp(-k*(u**2 + v**2)**(5/6))
    H[M + v, N - u] = np.exp(-k*(u**2 + v**2)**(5/6))
    H[M + v, N + u] = np.exp(-k*(u**2 + v**2)**(5/6))
    #because of symmetry, the distances are the same radially from center
  #end for x
#end for y

ply.imshow(H, cmap='Greys_r')
ply.colorbar()
ply.title('H function')
ply.show()

ply.plot( H[N, M:Q] )
ply.title('Profile of H')
ply.show()

#Creating Butterworth filter
#Computing all distances from center, D(u, v)
D = np.zeros((P, Q), dtype = float)
for v in range(0, M):
  for u in range(0, N):
    D[M + v, N + u] = np.sqrt(u**2 + v**2)/2
    D[M + v, N - u] = np.sqrt(u**2 + v**2)/2
    D[M - v, N + u] = np.sqrt(u**2 + v**2)/2
    D[M - v, N - u] = np.sqrt(u**2 + v**2)/2
    #because of symmetry, the distances are the same radially from center
  #end for x
#end for y

ply.imshow(D, cmap = 'Greys_r')
ply.colorbar()
ply.title('Distances')
ply.show()

n = 5
D0 = 40

#Butterworth low-pass filter function:
butter = 1 / np.sqrt( 1 + (D / (D0))**(2*n) )

ply.imshow(butter, cmap='Greys_r')
ply.colorbar()
ply.title('Butterworth')
ply.show()

#Applying Butterworth filter to remove eventual outer spikes
H = H*butter

ply.plot(H[M, N:Q])
ply.title('Profile of H, after Butterworth')
ply.show()

ply.imshow(H, cmap='Greys_r')
ply.colorbar()
ply.title('Butterworth applied to H')
ply.show()

F =  FT / H #Applying inverse filtering

ply.imshow(np.log(np.sqrt(F.real**2 + F.imag**2)), cmap='Greys_r')
ply.colorbar()
ply.title('Result, inverse, freq.dom.')
ply.show()

resultImg = np.real(ft.ifft2(F))
resultImg = resultImg[0:M, 0:N]#Remove padding, and scale values
resultImg = func.scale(resultImg, 256).astype(np.uint8)

ply.imshow(resultImg, cmap='Greys_r')
ply.colorbar()
ply.title('Result, inverse')
ply.show()


import numpy as np

# See:
# Malherbe, C., & Vayatis, N. (2017). Global optimization of Lipschitz functions. 34th International Conference on Machine Learning, ICML 2017, 5(1972), 3592–3601.
# https://github.com/UBC-CS/lipo-python/blob/master/src/sequential.py

def adaptive_lipo(func, bounds, n, p=0.1):
    # dimension of the domain
    d = len(bounds)

    alpha = 0.01/d
    k_seq=(1+alpha)**np.arange(-10000,10000) # Page 16

    # preallocate the output arrays
    y = np.zeros(n) - np.Inf
    x = np.zeros((n, d))
    loss = np.zeros(n)
    k_arr = np.zeros(n)

    # the lower/upper bounds on each dimension
    bound_mins = np.array([bnd[0] for bnd in bounds])
    bound_maxs = np.array([bnd[1] for bnd in bounds])

    # initialization with randomly drawn point in domain and k = 0
    k = 0
    k_est = -np.inf
    u = np.random.rand(d)
    x_prop = u * (bound_maxs - bound_mins) + bound_mins
    x[0] = x_prop
    y[0] = func(x_prop)
    k_arr[0] = k

    upper_bound = lambda x_prop, y, x, k: np.min(y+k*np.linalg.norm(x_prop-x,axis=1))

    for t in np.arange(1, n):
        # draw a uniformly distributed random variable
        u = np.random.rand(d)
        x_prop = u * (bound_maxs - bound_mins) + bound_mins

        # check if we are exploring or exploiting
        if np.random.rand() > p: # enter to exploit w/ prob (1-p)
            # exploiting - ensure we're drawing from potential maximizers
            while upper_bound(x_prop, y[:t], x[:t], k) < np.max(y[:t]):
                u = np.random.rand(d)
                x_prop = u * (bound_maxs - bound_mins) + bound_mins
        else:
            pass
            # we keep the randomly drawn point as our next iterate
            # this is "exploration"
        # add proposal to array of visited points
        x[t] = x_prop
        y[t] = func(x_prop)
        loss[t] = np.max(y)

        # compute current number of tracked distances
        old_num_dist = (t * (t - 1)) // 2
        new_num_dist = old_num_dist + t

        new_x_dist = np.sqrt(np.sum((x[:t] - x[t])**2, axis=1))

        new_y_dist = np.abs(y[:t] - y[t])

        k_est = max(k_est, np.max(new_y_dist/new_x_dist))  # faster
        k = k_seq[np.argmax(k_seq >= k_est)]
        i_t = np.ceil(np.log(k_est)/np.log(1+alpha))
        k = (1+alpha)**i_t
        k_arr[t] = k

    output = {'loss': loss, 'x': x, 'y': y, 'k': k_arr}
    return output


from numpy import *

def loadDataSet(fileName):
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = list(map(float, curLine))
        dataMat.append(fltLine)
    return dataMat

def distEclud(vecA,vecB):
    return sqrt(sum(power(vecA - vecB, 2)))

def randCent(dataSet, k):
    n = shape(dataSet)[1]
    centroids = mat(zeros((k,n)))
    for j in range(n):
        minJ = min(dataSet[:,j])
        rangeJ = float(max(dataSet[:,j]) - minJ)
        centroids[:,j] = minJ + rangeJ * random.rand(k,1)
    return centroids

def kMeans(dataSet,k,distMeas=distEclud, createCent=randCent):
    m = shape(dataSet)[0]
    clusterAssement = mat(zeros((m,2)))
    centroids = createCent(dataSet, k)
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(m):
            minDist = inf; minIndex = -1
            for j in range(k):
                distJI = distMeas(centroids[j,:],dataSet[i,:])
                if distJI < minDist:
                    minDist = distJI; minIndex = j
            if clusterAssement[i,0] != minIndex : clusterChanged = True
            clusterAssement[i,:] = minIndex,minDist**2
        print(centroids)
        for cent in range(k):
            ptsInClust = dataSet[nonzero(clusterAssement[:,0].A == cent)[0]]
            centroids[cent,:] = mean(ptsInClust, axis=0)
    return centroids, clusterAssement

datMat = mat(loadDataSet('testSet.txt'))
myCentroids, clustAssing = kMeans(datMat, 4)


import numpy as np

def fit_MRF_pseudolikelihood(adj_exc,adj_inh,y):
    '''
    Fit a Markov random field using maximum pseudolikelihood estimation,
    also known as logistic regression. The conditional probabilities
    follow

    y_i ~ Logistic(B[0] + B[1] A1_{ij} y_j + A1[2] X_{ij} (1-y_j)
                   + B[3] A2_{ij} y_j + B[4] A3_{ij} (1-y_j) ),

    where A1 = adj_exc and A2 = adj_inh and each term is summed over
    j.

    Params
    ======
      adj_exc: excitatory adjacency matrix
      adj_inh: inhibitory adjacency matrix
      y: site variables, 0 or 1

    Returns
    =======
      B: logistic regression coefficients
    '''
    from sklearn.linear_model import LogisticRegression
    from sklearn import cross_validation
    if len(np.unique(y)) < 2:
        B=np.array([np.nan,np.nan,np.nan,np.nan,np.nan])
    else:
        N=y.shape[0]
        ytile=np.tile(y,(N,1)).T
        X1=np.array(np.sum(np.multiply(adj_exc,ytile),0)).flatten()
        X2=np.array(np.sum(np.multiply(adj_exc,1-ytile),0)).flatten()
        X3=np.array(np.sum(np.multiply(adj_inh,ytile),0)).flatten()
        X4=np.array(np.sum(np.multiply(adj_inh,1-ytile),0)).flatten()
        model=LogisticRegression(penalty='l2')
        X=np.column_stack((X1,X2,X3,X4))
        model.fit(X,y)
        B=np.hstack((model.intercept_, model.coef_.flatten()))
    return B

def predict_MRF(B, adj_exc, adj_inh, burn_in=4e3, steps=1e4,
                skip_multiple=3):
    '''
    Perform prediction with an MRF (Markov random field). Uses Gibbs sampling
    to sample from the distribution P(y) =1/Z exp( -H(y) ).

    The Hamiltonian is:

    H = \sum_{ij} y_i (B[0] + B[1] A1_{ji} y_j + A1[2] X_{ji} (1-y_j)
                       + B[3] A2_{ji} y_j + B[4] A3_{ji} (1-y_j))

    Params
    ======
      B: coefficients of the MRF
      adj_exc: excitatory adjacency matrix
      adj_inh: inhibitory adjacency matrix
      burn_in: number of burn-in steps to take (default: 4000)
      steps: total number of Gibbs steps to take (default: 10000)
      skip_multiple: skips skip_multiple * num_neuron steps between samples

    Returns
    =======
      ypostmean: posterior mean of state
    '''
    import numpy.random
    def gibbs_proba(y,B,adj_exc,adj_inh):
        term0=B[0]*np.dot(adj_exc.T,y)
        term1=B[1]*np.dot(adj_exc.T,(1-y))
        term2=B[2]*np.dot(adj_inh.T,y)
        term3=B[3]*np.dot(adj_inh.T,(1-y))
        e=B[4]+term0+term1+term2+term3
        return np.exp(e)/(np.exp(e)+1.0)

    N=adj_exc.shape[0]
    steps=int(steps)
    # run a Gibbs sampler
    y=np.random.rand(N,1)
    samples=np.zeros((N,steps))
    # zero diagonals (should be 0 already)
    np.fill_diagonal(adj_exc,0)
    np.fill_diagonal(adj_inh,0)
    for ii in range(steps):
        yt=y
        # Gibbs update
        proba=gibbs_proba(y,B,adj_exc,adj_inh)
        yt=np.array(np.random.rand(N,1) < proba,dtype=np.float)
        y=yt
        samples[:,ii]=y.flatten()
    # compute mle
    use_indices=np.arange(burn_in,steps,skip_multiple*N, dtype=int)
    final_samples=samples[:,use_indices]
    ypostmean=np.mean(final_samples,axis=1)
    return ypostmean

def fit_logistic_graph_features():
    pass

def get_node_features(adj_exc,adj_inh,normalize_centrality=True):
    '''
    Get node-based features to train logistic classifier

    Params
    ======
      adj_exc: excitatory adjacency matrix
      adj_inh: inhibitory adjacency matrix
      normalize_centrality: normalize relevant measures? (default: True)

    Returns
    =======
      X: numneuron x numfeatures array to be used with logistic regression
      X_labels
    '''
    import networkx as nx
    G_exc=nx.DiGraph(adj_exc)
    G_inh=nx.DiGraph(adj_inh)

    def dict_to_array(d):
        return np.array([d[i] for i in sorted(d)])

    def features(G,normalize_centrality):
        '''
        Returns the features we are interested in within a dict
        '''
        load_centrality=nx.load_centrality(G,normalized=normalize_centrality)
        betweenness_centrality=nx.betweenness_centrality(G,normalized=normalize_centrality)
        eigenvector_centrality=nx.eigenvector_centrality_numpy(G,normalized=normalize_centrality)
        closeness_centrality=nx.closeness_centrality(G,normalized=normalize_centrality)
        in_degree=G.in_degree()
        out_degree=G.out_degree()
        core_number=nx.core_number(G)
        clustering=nx.clustering(G)
        d={}
        d['in_degree']=in_degree
        d['out_degree']=out_degree
        d['load_centrality']=load_centrality
        d['betweennes_centrality']=betweennes_centrality
        d['eigenvector_centrality']=eigenvector_centrality
        d['closeness_centrality']=closeness_centrality
        d['core_number']=core_number
        return d

    # grab the features
    d_exc=features(G_exc)
    d_inh=features(G_inh)
    # setup some structures
    num_features=len(d_exc)+len(d_inh)
    num_nodes=G.number_of_nodes()
    X=np.zeros((num_nodes,num_features),dtype=np.float)
    X_labels=[]
    # fill in X and Xlabels
    feature_index=0
    for gclass in ('exc','inh'):
        if gclass == 'exc':
            d=d_exc
        else:
            d=d_inh
        for feature in sorted(d):
            X_labels.append(feature+"_"+gclass)
            X[:,feature_index]=dict_to_array(d[feature])
            feature_index+=1
    return X, X_labels


"""
quasi_newton_algorithm.py

Returns the minimizer of the function in the image domain
X0 - initial guess
func - anonimous function
func_grad - anonimous function gradient
N_iter_max - max number of iterations
"""

import numpy as np
from secant_algorithm import SecantAlgorithmAlphaLinesearch
from image_restoration_utils import CapIntensity, AreWeDoneYet

def srs(h_old, delta_X, delta_grad):

    delta_g = delta_grad;
    h_old_times_delta_g = h_old @ delta_g;
    top = np.outer(delta_X - h_old_times_delta_g, (delta_X - h_old_times_delta_g).T);
    bottom = delta_g.T @ (delta_X - h_old_times_delta_g);
    delta_h = top / bottom;

    return delta_h;

def dfp(h_old, delta_X, delta_grad):

    delta_g = delta_grad;
    h_old_times_delta_g = h_old @ delta_g;
    a_top = np.outer(delta_X, delta_X.T);
    a_bottom = delta_X.T @ delta_g;
    b_top = np.outer(h_old_times_delta_g, h_old_times_delta_g.T);
    b_bottom = delta_g.T @ h_old_times_delta_g;
    delta_h = a_top / a_bottom - b_top / b_bottom;

    return delta_h;

def bfgs(h_old, delta_X, delta_grad):

    delta_g = delta_grad;
    h_old_times_delta_g = h_old @ delta_g;
    delta_gT_times_delt_X = delta_g.T @ delta_X;
    a_top = delta_g.T @ h_old_times_delta_g;
    a_bottom = delta_gT_times_delt_X;
    b_top = np.outer(delta_X, delta_X.T);
    b_bottom = delta_gT_times_delt_X
    c_top = np.outer(delta_X,  h_old_times_delta_g.T) + np.outer(h_old_times_delta_g, delta_X.T);
    c_bottom = delta_gT_times_delt_X;
    delta_h = 1 + (a_top / a_bottom) * (b_top / b_bottom) - (c_top / c_bottom);

    return delta_h;

def QuasiNewtonAlgorithm(X0, func, func_grad, func_hessian, options):

    epsilon = 10e-6
    reset_dir_every_n_iter = X0.size;
    report = {};
    N_iter_max = options['N_iter_max'];
    tolerance_x = options['tolerance_x'];
    tolerance_y = options['tolerance_y'];
    bpp = options['bpp'];
    progress_x = np.zeros((N_iter_max + 1, X0.size));
    progress_y = np.zeros((N_iter_max + 1, 1));
    progress_x[0] = X0.ravel();
    progress_y[0] = func(X0);
    X_old = X0;
    h_old = np.eye(X0.size, X0.size); # approximation of inv(hessian)

    for iter_no in range(1, N_iter_max + 1):
        grad_old = func_grad(X_old);

        if (np.linalg.norm(grad_old) < epsilon):
            print('norm(grad) < epsilon in %d iterations, exit..' % (iter_no));
            break;

        if (iter_no == 1 or iter_no % (reset_dir_every_n_iter) == 0):
            d = -grad_old; # resetting directional vector
        else:
            d = -h_old @ grad_old; # conjugate directional vector

        alpha = -(grad_old.T @ d) / (d.T @ func_hessian(X_old) @ d); # step size, this formula valid only for quadratic function
        X = X_old + alpha * d;

        # Projection onto the box constraints of X
        X = CapIntensity(X, bpp);

        progress_x[iter_no] = X.ravel();
        progress_y[iter_no] = func(X);

        if (AreWeDoneYet(iter_no, progress_x, progress_y, tolerance_x, tolerance_y) == True):
            break;

        grad = func_grad(X);
        delta_X = X - X_old;
        delta_grad = grad - grad_old;
        delta_h = bfgs(h_old, delta_X, delta_grad);
        h = h_old + delta_h;
        X_old = X;
        h_old = h;

    report = {'N_iter_max' : N_iter_max, 'iter_no' : iter_no, 'X0' : X0, 'X' : X, 'progress_x' : progress_x, 'progress_y' : progress_y};
    return (X, report);

def QuasiNewtonAlgorithmLinesearch(X0, func, func_grad, options):

    epsilon = 10e-6
    reset_dir_every_n_iter = X0.size;
    report = {};
    N_iter_max = options['N_iter_max'];
    tolerance_x = options['tolerance_x'];
    tolerance_y = options['tolerance_y'];
    bpp = options['bpp'];
    progress_x = np.zeros((N_iter_max + 1, X0.size));
    progress_y = np.zeros((N_iter_max + 1, 1));
    progress_x[0] = X0.ravel();
    progress_y[0] = func(X0);
    X_old = X0;
    h_old = np.eye(X0.size, X0.size); # approximation of inv(hessian)

    for iter_no in range(1, N_iter_max + 1):
        grad_old = func_grad(X_old);

        if (np.linalg.norm(grad_old) < epsilon):
            print('norm(grad) < epsilon in %d iterations, exit..' % (iter_no));
            break;

        if (iter_no == 1 or iter_no % (reset_dir_every_n_iter) == 0):
            d = -grad_old; # resetting directional vector
        else:
            d = -h_old @ grad_old; # conjugate directional vector

        alpha = SecantAlgorithmAlphaLinesearch(X_old, func_grad, d); # step size
        X = X_old + alpha * d;

        # Projection onto the box constraints of X
        X = CapIntensity(X, bpp);

        progress_x[iter_no] = X.ravel();
        progress_y[iter_no] = func(X);

        if (AreWeDoneYet(iter_no, progress_x, progress_y, tolerance_x, tolerance_y) == True):
            break;

        grad = func_grad(X);
        delta_X = X - X_old;
        delta_grad = grad - grad_old;
        delta_h = bfgs(h_old, delta_X, delta_grad);
        h = h_old + delta_h;
        X_old = X;
        h_old = h;

    report = {'N_iter_max' : N_iter_max, 'iter_no' : iter_no, 'X0' : X0, 'X' : X, 'progress_x' : progress_x, 'progress_y' : progress_y};
    return (X, report);


import numpy as np

class NaiveBayesClassifier:

    def __init__(self):
        pass

    def separate_classes(self, X, y):
        """
        Separates the dataset in to a subset of data for each class.

        Parameters:
        ------------
        X- array, list of features
        y- list, target

        Returns:
        A dictionnary with y as keys and assigned X as values.
        """
        separated_classes = {}
        for i in range(len(X)):
            feature_values = X[i]
            class_name = y[i]
            if class_name not in separated_classes:
                separated_classes[class_name] = []
            separated_classes[class_name].append(feature_values)
        return separated_classes

    def stat_info(self, X):
        """
        Calculates standard deviation and mean of features.

        Parameters:
        ------------
        X- array , list of features

        Returns:
        A dictionary with STD and Mean as keys and assigned features STD and Mean as values.
        """
        for feature in zip(*X):
            yield {
                'std' : np.std(feature),
                'mean' : np.mean(feature)
            }

    def fit (self, X, y):
        """
        Trains the model.

        Parameters:
        ----------
        X: array-like, training features
        y: list, target variable

        Returns:
        Dictionary with the prior probability, mean, and standard deviation of each class
        """

        separated_classes = self.separate_classes(X, y)
        self.class_summary = {}

        for class_name, feature_values in separated_classes.items():
            self.class_summary[class_name] = {
                'prior_proba': len(feature_values)/len(X),
                'summary': [i for i in self.stat_info(feature_values)],
            }
        return self.class_summary

    def distribution(self, x, mean, std):
        """
        Gaussian Distribution Function

        Parameters:
        ----------
        x: float, value of feature
        mean: float, the average value of feature
        stdev: float, the standard deviation of feature

        Returns:
        A value of Normal Probability
        """

        exponent = np.exp(-((x-mean)**2 / (2*std**2)))

        return exponent / (np.sqrt(2*np.pi)*std)

    def predict(self, X):
        """
        Predicts the class.

        Parameters:
        ----------
        X: array-like, test data set

        Returns:
        -----------
        List of predicted class for each row of data set
        """
        MAPs = []

        for row in X:
            joint_proba = {}

            for class_name, features in self.class_summary.items():
                total_features =  len(features['summary'])
                likelihood = 1

                for idx in range(total_features):
                    feature = row[idx]
                    mean = features['summary'][idx]['mean']
                    stdev = features['summary'][idx]['std']
                    normal_proba = self.distribution(feature, mean, stdev)
                    likelihood *= normal_proba
                prior_proba = features['prior_proba']
                joint_proba[class_name] = prior_proba * likelihood

            MAP = max(joint_proba, key= joint_proba.get)
            MAPs.append(MAP)

        return MAPs

    def accuracy(self, y_test, y_pred):
        """
        Calculates model's accuracy.

        Parameters:
        ------------
        y_test: actual values
        y_pred: predicted values

        Returns:
        ------------
        A number between 0-1, representing the percentage of correct predictions.
        """

        true_true = 0

        for y_t, y_p in zip(y_test, y_pred):
            if y_t == y_p:
                true_true += 1
        return true_true / len(y_test)


import numpy as np

class TensorMethods:
    """
    Implements two methods to calculate tensors from two matrices
    """
    def tensor1(A, B, display=False):
        """
        Calculates the product of two matrices using the first method (see HW Problem 3.6).

        Using numpy's einsum() function it calculates the product with the given axis.
        """
        mat = np.einsum('irl,lrj->ij', A, B)
        if display:
            print(mat)
        return mat

    def tensor2(A, B, display=False):
        """
        Calculates the product of two matrices using the second method (see HW Problem 3.6).

        Applying the product on the second axis of method 1 will give us our 1-D Array.
        """
        mat = np.product(TensorMethods.tensor1(A, B), axis=1)
        if display:
            print(mat)
        return mat

def main():
    # Test functions
    A = np.array([[[1,2], [3,4]], [[1,2], [3,4]]])
    B = np.array([[[1,2,3], [4,5,6]], [[1,2,3], [4,5,6]]])
    TensorMethods.tensor1(A, B, True)
    TensorMethods.tensor2(A, B, True)

if __name__ == "__main__":
    main()


################################################################################
#  Github: https://github.com/MaxInGaussian/GomPlex
#  Author: Max W. Y. Lam (maxingaussian@gmail.com)
################################################################################

import numpy as np

__all__ = [
    "Metric"
]

class Metric(object):

    metrics = [
        "mse",
        "rmse",
        "nmse",
        "mae",
        "nlpd",
        "nlpd_mse",
        "nlpd_nmse",
        "nlml"
    ]

    def __init__(self, metric, gp=None):
        assert metric in self.metrics, "Invalid metric!"
        self.metric = metric
        self.gp = gp

    def eval(self, target, mu_pred, std_pred):
        return getattr(self, self.metric)(target, mu_pred, std_pred)

    def mse(self, target, mu_pred, std_pred):
        mse_real = np.mean(np.real(target-mu_pred)**2)
        mse_imag = np.mean(np.imag(target-mu_pred)**2)
        return mse_real/2+mse_imag/2

    def rmse(self, target, mu_pred, std_pred):
        mse_real = np.mean(np.real(target-mu_pred)**2)
        mse_imag = np.mean(np.imag(target-mu_pred)**2)
        return (mse_real/2+mse_imag/2)**0.5

    def nmse(self, target, mu_pred, std_pred):
        mse_real = np.mean(np.real(target-mu_pred)**2)
        nmse_real = mse_real/np.var(np.real(target))
        if(np.var(np.imag(target)) > 0):
            mse_imag = np.mean(np.imag(target-mu_pred)**2)
            nmse_imag = mse_imag/np.var(np.imag(target))
            return nmse_real/2+nmse_imag/2
        return nmse_real

    def mae(self, target, mu_pred, std_pred):
        mae = np.mean(np.abs(target.real-mu_pred.real))+\
            np.mean(np.abs(target.imag-mu_pred.imag))
        return mae/2

    def nlpd(self, target, mu_pred, std_pred):
        nlpd = np.mean(((target-mu_pred)/std_pred)**2+2*np.log(std_pred))
        nlpd = 0.5*(np.log(2*np.pi)+nlpd)
        return np.absolute(nlpd)

    def nlpd_mse(self, target, mu_pred, std_pred):
        return self.mse(target, mu_pred, std_pred)-\
            np.exp(-self.nlpd(target, mu_pred, std_pred))

    def nlpd_nmse(self, target, mu_pred, std_pred):
        return self.nmse(target, mu_pred, std_pred)-\
            np.exp(-self.nlpd(target, mu_pred, std_pred))

    def nlml(self, target, mu_pred, std_pred):
        if(self.gp.mean_only):
            return self.mse(target, mu_pred, std_pred)
        noise = self.gp.noise_real+self.gp.noise_imag*1j
        goodness_of_fit = (target.conj().T.dot(target-mu_pred))/noise
        covariance_penalty = np.sum(np.log(np.diagonal(self.gp.T)))
        noise_penalty = (self.gp.N-self.gp.M)*np.log(noise)
        nlml = goodness_of_fit+covariance_penalty+noise_penalty
        return np.absolute(nlml[0, 0])


from __future__ import division, print_function
import csv, os, sys
import numpy as np
from SVM import SVM
filepath = os.path.dirname(os.path.abspath(__file__))

def readData(filename, header=True):
    data, header = [], None
    with open(filename, 'rb') as csvfile:
        spamreader = csv.reader(csvfile, delimiter=',')
        if header:
            header = spamreader.next()
        for row in spamreader:
            data.append(row)
    return (np.array(data), np.array(header))

def calc_acc(y, y_hat):
    idx = np.where(y_hat == 1)
    TP = np.sum(y_hat[idx] == y[idx])
    idx = np.where(y_hat == -1)
    TN = np.sum(y_hat[idx] == y[idx])
    return float(TP + TN)/len(y)

def main(filename='data/iris-virginica.txt', C=1.0, kernel_type='linear', epsilon=0.001):
    # Load data
    (data, _) = readData('%s/%s' % (filepath, filename), header=False)
    data = data.astype(float)

    # Split data
    X, y = data[:,0:-1], data[:,-1].astype(int)

    # Initialize model
    model = SVM()

    # Fit model
    support_vectors, iterations = model.fit(X, y)

    # Support vector count
    sv_count = support_vectors.shape[0]

    # Make prediction
    y_hat = model.predict(X)

    # Calculate accuracy
    acc = calc_acc(y, y_hat)

    print("Support vector count: %d" % (sv_count))
    print("bias:\t\t%.3f" % (model.b))
    print("w:\t\t" + str(model.w))
    print("accuracy:\t%.3f" % (acc))
    print("Converged after %d iterations" % (iterations))

if __name__ == '__main__':
    if ('--help' in sys.argv) or ('-h' in sys.argv):
        print("")
        print("Trains a support vector machine.")
        print("Usage: %s FILENAME C kernel eps" % (sys.argv[0]))
        print("")
        print("FILENAME: Relative path of data file.")
        print("C:        Value of regularization parameter C.")
        print("kernel:   Kernel type to use in training.")
        print("          'linear' use linear kernel function.")
        print("          'quadratic' use quadratic kernel function.")
        print("eps:      Convergence value.")
    else:
        kwargs = {}
        if len(sys.argv) > 1:
            kwargs['filename'] = sys.argv[1]
        if len(sys.argv) > 2:
            kwargs['C'] = float(sys.argv[2])
        if len(sys.argv) > 3:
            kwargs['kernel_type'] = sys.argv[3]
        if len(sys.argv) > 4:
            kwargs['epsilon'] = float(sys.argv[4])
        if len(sys.argv) > 5:
            sys.exit("Not correct arguments provided. Use %s -h for more information"
                     % (sys.argv[0]))
        main(**kwargs)


# -*- coding: utf-8 -*-
"""
Numerical Integration testing - Trapezoidal rule with constant step h for creating mesh points
Trapezoidal rule integration precision can be roughly estimated as ~ h**2
@author: ssklykov
"""
# %% Import Section
import numpy as np
from sampleFunctions import SampleFuncIntegr as sfint
import inspect
import matplotlib.pyplot as plt

# %% Algorithm implementation
"""Trapezoidal Integration implementation. It demands [a,b] of interval for integration; h - step size, y - function or
method returning single float number and accepting single float number"""
def TrapezoidalIntegr(a:float,b:float,h:float,y,nDigits:int=3):
    if (a >= b) and (int((b-a)/h) <= 1):
        print("Incosistent interval assigning [a,b] or step size h")
        return None # returning null object instead of any result, even equal to zero
    elif not((inspect.isfunction(y)) or (inspect.ismethod(y))):
        print("Passed function y(x) isn't the defined method or function")
        return None
    else:
        nPoints = int((b-a)/h) + 1
        intSum = 0.0
        for i in range(1,nPoints-1):
            x = a + i*h; intSum += y(x)
        intSum += (y(a) + y(b))/2
        intSum *= h
        return round(intSum,nDigits)

# %% Parameters for testing
nDigits = 2; a = 0; b = 2; nSample = 1; h = 0.05
nPoints = int((b-a)/h) + 1
# print(nPoints," - nPoints inside the interval [a,b]")
x = np.zeros(nPoints); y = np.zeros(nPoints)
fClass = sfint(nDigits,nSample) # making sample of the class contained the sample function
# making x and y values for plotting
for i in range(nPoints):
    x[i] = a + i*h; y[i] = fClass.sampleF(x[i])

# plot sample function from interval [a,b]
fig = plt.figure(); plt.plot(x,y); plt.grid()

# %% Testing
integral = TrapezoidalIntegr(a,b,h,fClass.sampleF,nDigits)
print(integral," - calculated integral value")
print("1 - exact value from Newton-Leibniz equation F(b) - F(a)") # F(a) = 0; F(b) = 1; F(x) = x^3 - x^2 - 1.5*x


'''
Dimensions in Arrays

* A dimension in arrays is one level of array depth (nested arrays).
* nested array: are arrays that have arrays as their elements.

0-D Arrays

* 0-D arrays, or Scalars, are the elements in an array. Each value in an array is a 0-D array.

1-D Arrays

* An array that has 0-D arrays as its elements is called uni-dimensional or 1-D array.

2-D Arrays

* An array that has 1-D arrays as its elements is called a 2-D array.

3-D Arrays

* An array that has 2-D arrays (matrices) as its elements is called 3-D array.

Check Number of Dimensions?

* NumPy Arrays provides the ndim attribute that returns an integer that tells us how many dimensions the array have.

'''
import numpy as np

# Create a 0-D array with value 42
arr0 = np.array(42)
print(arr0)
print(type(arr0))
print('Dimensión: ', arr0.ndim)
print('\n')

# Create a 1-D array containing the values 1,2,3,4,5:
arr1 = np.array([1, 2, 3, 4, 5])
print(arr1)
print(type(arr1))
print('Dimensión: ', arr1.ndim)
print('\n')

# Create a 2-D array containing two arrays with the values 1,2,3 and 4,5,6:
arr2 = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2)
print(type(arr2))
print('Dimensión: ', arr2.ndim)
print('\n')

# Create a 3-D array with two 2-D arrays, both containing two arrays with the values 1,2,3 and 4,5,6:
arr3 = np.array([ [[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]] ])
print(arr3)
print(type(arr3))
print('Dimensión: ', arr3.ndim)