import random
import numpy as np

def get_step_costs(rmap, steps):
    return [rmap.get_cost(*s) for s in steps]

def get_possible_steps(theta, n_points=100, step_size=.002):
    # smaller stepsize since there is no learning_rate param
    # for traditional hill climbing algorithms, evening the playfield
    c_s = 2 * np.pi / n_points
    return [(theta[0] + step_size * np.sin(c_s * i),
             theta[1] + step_size * np.cos(c_s * i))
            for i in range(n_points)]

def get_nesw_steps(theta, step_size=.005):
    try:
        step_north = (theta[0] + step_size, theta[1])
        step_south = (theta[0] - step_size, theta[1])
        step_east = (theta[0], theta[1] + step_size)
        step_west = (theta[0], theta[1] - step_size)
    except IndexError:
        print('The boundary of elevation map has been reached')
        return None

    return step_north, step_east, step_south, step_west

def calculate_gradient(rmap, theta, j_history, n_iter):
    cost = rmap.get_cost(*theta)
    elevation = rmap.get_elevation(*theta)
    j_history[n_iter] = [elevation, theta[0], theta[1]]

    step_costs = get_step_costs(rmap, get_nesw_steps(theta))

    if cost <= 0 or step_costs is None:
        return None

    lat_slope = step_costs[0] / step_costs[2] - 1
    lon_slope = step_costs[1] / step_costs[3] - 1
    print(f'Elevation at {theta} is {elevation}')

    return np.array((lat_slope, lon_slope))

def gradient_descent(rmap, theta, alpha=.01, num_iters=10000):
    j_history = np.zeros(shape=(num_iters, 3))

    for i in range(num_iters):
        slope = calculate_gradient(rmap, theta, j_history, i)
        if slope is None:
            break

        step = -alpha * slope
        print(f'({i}/{num_iters}): Update is {step}')
        theta += step

    return theta, j_history[:i]

def gradient_descent_w_momentum(rmap, theta, alpha=.01, mu=.99, num_iters=10000):
    j_history = np.zeros(shape=(num_iters, 3))
    velocity = np.zeros_like(theta)

    for i in range(num_iters):
        slope = calculate_gradient(rmap, theta, j_history, i)
        if slope is None:
            break

        velocity = mu * velocity - alpha * slope
        print(f'({i}/{num_iters}): Update is {velocity}')

        theta += velocity

    return theta, j_history[:i]

def gradient_descent_w_nesterov(rmap, theta, alpha=.01, mu=.99, num_iters=10000):
    j_history = np.zeros(shape=(num_iters, 3))
    velocity = np.zeros_like(theta)
    v_prev = np.zeros_like(theta)

    for i in range(num_iters):
        slope = calculate_gradient(rmap, theta, j_history, i)
        if slope is None:
            break

        v_prev = np.copy(velocity)
        velocity = mu * velocity - alpha * slope

        step = -mu * v_prev + (1 + mu) * velocity
        print(f'({i}/{num_iters}): Update is {step}')

        theta += step

    return theta, j_history[:i]

def adagrad(rmap, theta, alpha=.01, epsilon=1e-8, num_iters=10000):
    j_history = np.zeros(shape=(num_iters, 3))
    cache = np.zeros_like(theta)

    for i in range(num_iters):
        slope = calculate_gradient(rmap, theta, j_history, i)
        if slope is None:
            break

        cache += slope**2

        step = -alpha * slope / (np.sqrt(cache) + epsilon)
        print(f'({i}/{num_iters}): Update is {step}')

        theta += step

    return theta, j_history[:i]

def RMSprop(rmap, theta, alpha=.001, epsilon=1e-8, decay_rate=.99, num_iters=10000):
    j_history = np.zeros(shape=(num_iters, 3))
    cache = np.zeros_like(theta)

    for i in range(num_iters):
        slope = calculate_gradient(rmap, theta, j_history, i)
        if slope is None:
            break

        cache = decay_rate * cache + (1 - decay_rate) * slope**2

        step = -alpha * slope / (np.sqrt(cache) + epsilon)
        print(f'({i}/{num_iters}): Update is {step}')

        theta += step

    return theta, j_history[:i]

def adam(rmap, theta, alpha=.001, epsilon=1e-8, beta1=.9,
         beta2=.999, num_iters=10000):
    j_history = np.zeros(shape=(num_iters, 3))
    m, v = np.zeros_like(theta), np.zeros_like(theta)

    # bias correction
    mt, vt = np.zeros_like(theta), np.zeros_like(theta)

    for i in range(num_iters):
        slope = calculate_gradient(rmap, theta, j_history, i)
        if slope is None:
            break

        t = i + 1

        m = beta1 * m + (1 - beta1) * slope
        mt = m / (1 - beta1**t)
        v = beta2 * v + (1 - beta2) * slope**2
        vt = v / (1 - beta2**t)

        step = -alpha * mt / (np.sqrt(vt) + epsilon)
        print(f'({i}/{num_iters}): Update is {step}')

        theta += step

    return theta, j_history[:i]

def stochastic_hill_climb(rmap, theta, num_iters=10000):
    cost = rmap.get_cost(*theta)
    elevation = rmap.get_elevation(*theta)
    j_history = np.zeros(shape=(num_iters, 3))
    j_history[0] = [elevation, theta[0], theta[1]]

    for i in range(1, num_iters):
        steps = get_possible_steps(theta)
        step_costs = get_step_costs(rmap, steps)

        # extra loop for randomness to settle in
        # added this because without this, it is extremely prone to stucking
        for j in range(50):
            step, step_cost = random.choice(list(zip(steps, step_costs)))

            if step_cost <= cost:
                theta = step
                cost = step_cost

        elevation = rmap.get_elevation(*theta)
        j_history[i] = [elevation, theta[0], theta[1]]

        print(f'Elevation at {theta} is {elevation}')
        print(f'({i}/{num_iters}): Update is {theta}')

    return theta, j_history[:i]

def tabu_search(rmap, theta, tabu_size=10, num_iters=10000):
    elevation = rmap.get_elevation(*theta)
    j_history = np.zeros(shape=(num_iters, 3))
    j_history[0] = [elevation, theta[0], theta[1]]

    best_t = cand_t = theta
    tabu_list = [(tuple(theta))]

    for i in range(1, num_iters):
        steps = get_possible_steps(cand_t)

        cand_t = steps[0]
        for s in steps:
            if s not in tabu_list and rmap.get_cost(*s) < rmap.get_cost(*cand_t):
                cand_t = s

        if rmap.get_cost(*cand_t) < rmap.get_cost(*best_t):
            best_t = cand_t

        tabu_list.append(cand_t)
        if len(tabu_list) < tabu_size:
            del tabu_list[0]

        elevation = rmap.get_elevation(*best_t)
        j_history[i] = [elevation, best_t[0], best_t[1]]

        print(f'Elevation at {best_t} is {elevation}')
        print(f'({i}/{num_iters}): Update is {best_t}')

    return best_t, j_history[:i]

def simulated_annealing(rmap, theta, alpha=.99, temp=1,
                        min_temp=1e-6, num_iters=10000):
    cost = rmap.get_cost(*theta)
    elevation = rmap.get_elevation(*theta)
    j_history = np.zeros(shape=(num_iters, 3))
    j_history[0] = [elevation, theta[0], theta[1]]

    def prob(c, n_c, t):
        p = np.e**((c - n_c) / t)
        if p >= np.random.random():
            return True
        return False

    for i in range(1, num_iters):
        if temp < min_temp:
            break

        steps = get_possible_steps(theta)
        step_costs = get_step_costs(rmap, steps)
        for j in range(50):

            step, step_cost = random.choice(list(zip(steps, step_costs)))
            if prob(cost, step_cost, temp):
                theta = step
                cost = step_cost

        elevation = rmap.get_elevation(*theta)
        j_history[i] = [elevation, theta[0], theta[1]]

        print(f'Elevation at {theta} is {elevation}')
        print(f'({i}/{num_iters}): Update is {theta}')

        temp *= alpha

    return theta, j_history[:i]

def genetic_alg(rmap, theta):
    pass

def particle_swarm(rmap, theta):
    pass


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import timeit
def DFT(x):
    #Zwykła dyskretna transformata fouriera
    x = np.asarray(x, dtype=float)
    N = x.shape[0]
    n = np.arange(N)
    k = n.reshape((N, 1))
    M = np.exp(-2j * np.pi * k * n / N)
    #print np.dot(M,x)
    #mnożenie macierzowe
    return np.dot(M, x)

def FFT(x):
    x = np.asarray(x, dtype=float)
    N = x.shape[0]
    #ilość etapów  rosnie Log2(N)
    if np.log2(N) % 1 > 0:
        raise ValueError("size of x must be a power of 2")

    #N_min = min(N,32)
    #ustawia ile blok będzie przyjmować danych
    N_min = 2

    # Perform an O[N^2] DFT on all length-N_min sub-problems at once
    n = np.arange(N_min)
    k = n[:, None]
    M = np.exp(-2j * np.pi * n * k / N_min)
    #obliczenie pierwszej etapu
    #dot - mnożenie
    # reshape - tworzenie macierzy wiersz parzystych elementów , nieprzystych elementów
    # shape[0] - wierszowo , shape[1] - kolumnowo
    X = np.dot(M, x.reshape((N_min, -1)))
   	#wykonanie N etapów
    while X.shape[0] < N:
    	#parzysty X(k) gdzie k = 0,1,2,3,4, .... , N/2 -1
        X_even = X[:, :X.shape[1] / 2]
        #nieparzysty X(k + N/2) gdzie k = 0,1,2,3,4, .... , N/2 -1
        X_odd = X[:, X.shape[1] / 2:]
        factor = np.exp(-1j * np.pi * np.arange(X.shape[0])
                        / X.shape[0])[:, None]
        #mnożenie macierzowe
        X = np.vstack([X_even + factor * X_odd,
                       X_even - factor * X_odd])
    #łączenie wierszy jedną całość
    return X.ravel()

if __name__ == "__main__":
	x = np.random.random(1024)
	dft = DFT(x)
	setup = ''
	time_dft = timeit.Timer(lambda: DFT(x)).timeit(number=1)
	#print "DFT = ", dft
	fft =FFT(x)
	time_fft = timeit.Timer(lambda: FFT(x)).timeit(number=1)
	time_fft_lib = timeit.Timer(lambda: np.fft.fft(x)).timeit(number=1)
	print " DFT = ",time_dft, " us \n fft = ",time_fft," us \n fft lib = ", time_fft_lib," us "
	#print "fft = ", fft


# -*- coding: utf-8 -*-
"""
Created on Wed May 12 16:07:41 2021

Computes the wilcoxon signed rank test of the results.
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm, kurtosis, wilcoxon

plt.style.use('default')

import scipy
print(scipy.__version__)

path = r'./path/to/file'
df = pd.read_csv(path)
pipelines = df['pipeline'].unique()

fig, axes = plt.subplots(1, 3, sharex=True,sharey = True, figsize =(12,3), dpi = 200)
colours = ['tab:blue', 'tab:orange', 'tab:green']
for p in range(pipelines.shape[0]):
    scores = df[df['pipeline'] == pipelines[p]]['score'].to_numpy()
    space = np.linspace(0, 1.1, 110)
    axes[p].plot(space, norm.pdf(space,  loc = np.mean(scores), scale = np.std(scores)), color = 'tab:orange', label = 'superimposed normal distribution')
    axes[p].hist(scores, density = True, label = 'density histogram', color = 'tab:blue')
    axes[p].set_title("{}\nmean: {}  std: {}   kur: {}".format(pipelines[p], np.round(np.mean(scores), 2), np.round(np.std(scores), 2), np.round(kurtosis(scores), 2)), color = colours[p])
    axes[p].grid()

# Add common lables and legend
fig.text(0.5, -0.20, 'ROC AUC score', ha='center')
axes.flatten()[-2].legend(loc='upper center', bbox_to_anchor=(0.5, -0.2), ncol=3)

# Shift plots down for common title
fig.subplots_adjust(top=0.70)
fig.suptitle('The distribution of the scores per pipeline\nBNCI_1')

axes[0].set_ylabel("Normalized count")

plt.show()

# Statistical tests
tsls_1 = df[df['pipeline'] == 'Tangent space LDA']['score'].to_numpy()
resp_1 = df[df['pipeline'] == 'Response shrinkage']['score'].to_numpy()
prot_1 = df[df['pipeline'] == 'Super trial shrinkage']['score'].to_numpy()

check = df.groupby(['pipeline', 'subject'])['score'].aggregate([np.mean]).reset_index()

tsls = check[check['pipeline'] == 'Tangent space LDA']['mean'].to_numpy()
resp = check[check['pipeline'] == 'Response shrinkage']['mean'].to_numpy()
prot = check[check['pipeline'] == 'Super-trial shrinkage']['mean'].to_numpy()

print(tsls_1.shape)

# Calculate p values
_, pvalue = wilcoxon(tsls, resp, alternative= "two-sided")
print("The p value from the double sided dependent samples Wilcoxon signed-rank test between the baseline and the response shrinkage is {}.\n".format(pvalue))

_, pvalue = wilcoxon(tsls, prot, alternative= "two-sided")
print("The p value from the double sided dependent samples Wilcoxon signed-rank test between the baseline and the prototype shrinkage is {}.\n".format(pvalue))


import copy

import numpy as np
from matplotlib import pyplot as plt

u = 5.0
D = 1.0
n = 10
L = 2.0
dx = L / n
Pe = u * dx / D
omega = 1.6
EPS = 0.01
i_max = 1000

aw = u / dx + D / dx**2
ap = - u / dx - 2 * D / dx**2
ae = D / dx**2
# aw = 3
# ap = -4
# ae = 1

a = np.full(n + 1, aw)
b = np.full(n + 1, ap)
c = np.full(n + 1, ae)
a[-1] = c[0] = 0
b[0] = b[-1] = 1

f = np.zeros(n + 1)
x = np.zeros(n + 1)
for i in range(n + 1):
    x[i] = dx * i
    f[i] = (np.exp(Pe * dx * i / L) - 1) / (np.exp(Pe) - 1)

f_TDMA = np.zeros(n + 1)
f_SOR = np.zeros(n + 1)

b_TDMA = np.zeros(n + 1)
b_SOR = np.zeros(n + 1)
b_TDMA[-1] = 1
b_SOR[-1] = 1

if __name__ == '__main__':
    # TDMA method
    gam = np.zeros(n)
    bet = b[0]
    f_TDMA[0] = b_TDMA[0] / bet
    for i in range(1, n + 1):
        gam[i - 1] = c[i - 1] / bet
        bet = b[i] - a[i] * gam[i - 1]
        f_TDMA[i] = (b_TDMA[i] - a[i] * f_TDMA[i - 1]) / bet

    for j in reversed(range(1, n)):
        f_TDMA[j] -= gam[j] * f_TDMA[j + 1]

    # SOR method
    for i in range(i_max):
        M = 0
        f_tmp = copy.copy(f_SOR)
        for j in range(n + 1):
            if j == 0 or j == n:
                f_SOR[j] = f_tmp[j] * (1 - omega) + b_SOR[j] * omega
                r1 = np.abs((f_SOR[j] - f_tmp[j]) / f_SOR[j])
                if r1 < EPS:
                    M += 1

            else:
                f_SOR[j] = f_tmp[j] * (1 - omega) + (b_SOR[j] - c[j] * f_tmp[j + 1] - a[j] * f_SOR[j - 1]) / b[j] * omega
                r2 = np.abs((f_SOR[j] - f_tmp[j]) / f_SOR[j])
                if r2 < EPS:
                    M += 1

        if M == n:
            break

    print("fin")

    # graph1
    fig1, ax1 = plt.subplots()
    p1 = plt.plot(x, f, label="true")
    p2 = plt.plot(x, f_TDMA, label="TDMA")
    plt.title("TDMA method")
    plt.legend(loc='upper left')
    plt.xlim(0, 2)
    plt.ylim(0, 1)
    plt.xticks([0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0], ["0.0", "0.lecture10", "0.4", "0.6", "0.8", "1.0", "1.lecture10", "1.4", "1.6", "1.8", "lecture10.0"])
    ax1.set_xticks(np.linspace(0, 2, 11), minor=True)
    plt.yticks([0.0, 0.5, 1.0], ["0.0", "0.5", "1.0"])
    ax1.set_yticks(np.linspace(0, 1, 3), minor=True)
    plt.xlabel('x')
    plt.ylabel('f')
    plt.show()

    # graph2
    fig2, ax2 = plt.subplots()
    q1 = plt.plot(x, f, label="true")
    q2 = plt.plot(x, f_SOR, label="SOR", color="green")
    plt.title("TDMA method")
    plt.legend(loc='upper left')
    plt.xlim(0, 2)
    plt.ylim(0, 1)
    plt.xticks([0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0], ["0.0", "0.lecture10", "0.4", "0.6", "0.8", "1.0", "1.lecture10", "1.4", "1.6", "1.8", "lecture10.0"])
    ax2.set_xticks(np.linspace(0, 2, 11), minor=True)
    plt.yticks([0.0, 0.5, 1.0], ["0.0", "0.5", "1.0"])
    ax2.set_yticks(np.linspace(0, 1, 3), minor=True)
    plt.xlabel('x')
    plt.ylabel('f')
    plt.show()


##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
##                    Univariate outlier detection
## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#%% Generate oulier-infested data
import numpy as np

X = np.random.normal(40, 1, (1500,1))
X[200:300] = X[200:300] +8; X[1000:1150] = X[1000:1150] + 8

# plot
import matplotlib.pyplot as plt
plt.plot(X, '.-')
plt.xlabel('sample #'), plt.ylabel('variable measurement')
plt.title('Raw measurements')

#%% 3-sigma rule
# location & spread
mu = np.mean(X)
sigma = np.std(X)

# mean and std
print('Estimated mean = ', mu)
print('Estimated standard deviation = ', sigma)

# plot
plt.figure()
plt.plot(X, '.-', alpha=0.8, markeredgecolor='k', markeredgewidth=0.1, ms=3)
plt.hlines(mu, 0, 1500, colors='m', linestyles='dashdot', label='Mean')
plt.hlines(mu+3*sigma, 0, 1500, colors='r', linestyles='dashdot', label='Upper bound')
plt.hlines(mu-3*sigma, 0, 1500, colors='r', linestyles='dashed', label='Lower bound')

plt.xlabel('sample #'), plt.ylabel('Variable measurement')
plt.xlim((0,1500))
plt.title('3-sigma bounds')
plt.legend(loc='upper right')

#%% hampel identifier
# compute median and MAD
from scipy import stats

median = np.median(X)
sigma_MAD = stats.median_absolute_deviation(X) # default scaling of 1.4826 is built-in

# median & sigma_MAD
print('Estimated robust location = ', median)
print('Estimated robust spread = ', sigma_MAD)

# plot
plt.figure()
plt.plot(X, '.-', alpha=0.8, markeredgecolor='k', markeredgewidth=0.1, ms=3)
plt.hlines(median, 0, 1500, colors='m', linestyles='dashdot', label='Mean')
plt.hlines(median+3*sigma_MAD, 0, 1500, colors='r', linestyles='dashdot', label='Upper bound')
plt.hlines(median-3*sigma_MAD, 0, 1500, colors='r', linestyles='dashed', label='Lower bound')

plt.xlabel('sample #'), plt.ylabel('Variable measurement')
plt.xlim((0,1500))
plt.title('Hampel identifier bounds')
plt.legend(loc='upper right')


import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D

# learning rate
LR = 0.1
REAL_PARAMS = [1.2, 2.5]
INIT_PARAMS = [[5, 4], [5, 1], [2, 4.5]][0]

x = np.linspace(-1, 1, 200, dtype=np.float32)

# for visualize
# def y_fun(a, b): return a * x + b
# def tf_y_fun(a, b): return a * x + b

# test 2
# y_fun = lambda a, b: a * x**3 + b * x **2
# tf_y_fun = lambda a, b: a * x**3 + b * x**2

# test 3
y_fun = lambda a, b: np.sin(b*np.cos(a*x))
tf_y_fun = lambda a, b: tf.sin(b*tf.cos(a*x))

noise = np.random.randn(200) / 10
y = y_fun(*REAL_PARAMS) + noise

a, b = [tf.Variable(initial_value=p, dtype=tf.float32) for p in INIT_PARAMS]
pred = tf_y_fun(a, b)

# calculate cost
mse = tf.reduce_mean(tf.square(y - pred))
train_op = tf.train.GradientDescentOptimizer(LR).minimize(mse)

# list for record
a_list, b_list, cost_list = [], [], []

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for t in range(400):
        a_, b_, mse_ = sess.run([a, b, mse])
        # record parameter changes
        a_list.append(a_)
        b_list.append(b_)
        cost_list.append(mse_)
        # training
        result, _ = sess.run([pred, train_op])

# ------- Visualize ----------
print('a=', a_, 'b=', b_)
plt.figure(1)
plt.scatter(x, y, c='b')
plt.plot(x, result, 'r-', lw=2)

# 3D cost figure
fig = plt.figure(2)
ax = Axes3D(fig)

# parameter space
a3D, b3D = np.meshgrid(np.linspace(-2, 7, 30), np.linspace(-2, 7, 30))
cost3D = np.array([np.mean(np.square(y_fun(a_, b_) - y))
                   for a_, b_ in zip(a3D.flatten(), b3D.flatten())]).reshape(a3D.shape)

ax.plot_surface(a3D, b3D, cost3D, rstride=1,
                cmap=plt.get_cmap('rainbow'), alpha=0.5)

# init parameter place
ax.scatter(a_list[0], b_list[0], zs=cost_list[0], s=300, c='r')
ax.set_xlabel('a')
ax.set_ylabel('b')

# plot 3D Gradient
ax.plot(a_list, b_list, zs=cost_list, zdir='z', c='r', lw=3)
plt.show()


import numpy as np

EPS = 10 ** (-8)

class RMSProp():
    """ Implements the RMSProp optimizer.

    Note that this aglorithm uses coordinate-adaptive learning rates
    and, to do so, accumulates the squares of the gradient in a
    state vecor which is updated using learky average.

    Parameters
    ----------
    learning_rate : float, optional
        The initial learning rate to use for the parameter's updates
    gamma : float, optional
        Parameter for the leaky averaging

    Arguments
    ---------
    lr : float
        The initial learning rate to use for the parameter's updates
    gamma : float
        Parameter for the leaky averaging
    s_w : numpy.array
        State vector where the squares of the gradient for the weights
        are accumulated
    s_bias : numpy.array
        State vector where the squares of the gradient for the bias
        are accumulated
    reference_index : int
        Index of the neuron for which we want to save the learning rates
    learning_rates : dict
        Accumulator where the learning rates for the neuron specified by
        reference_index are stored at each update
    """
    def __init__(self, learning_rate=0.01, gamma=0.9):

        self.lr = learning_rate
        self.gamma = gamma
        self.s_w = None
        self.s_bias = None
        self.reference_index = 18
        self.learning_rates = {
            'weights': [],
            'bias': []
        }

    def update_weights(self, weights, grad):
        """ Updates the weights given the gradient.

        The update rule is:
        W = W - lr / (sqrt(s_w + EPS)) * gradient
        where EPS is just a small factor needed as to make sure
        that we never divide by zero

        Parameters
        ----------
        weights : numpy.array
            Current value for the weights
        grad : numpy.array
            Current value for the greadients

        Returns
        -------
        W : numpy.array
            The updated weights

        """
        if self.s_w is None:
            self.s_w = np.zeros(weights.shape)
        self.s_w = self.gamma * self.s_w + (1 - self.gamma) * (grad**2)
        lr = self.lr / np.sqrt(self.s_w + EPS)
        self._save_one_learning_rate(lr)
        W = weights - lr*grad
        return W

    def update_bias(self, bias, grad):
        """ Updates the biases given the gradient.

        The update rule is:
        bias = bias - lr / (sqrt(s_bias + EPS)) * gradient
        where EPS is just a small factor needed as to make sure
        that we never divide by zero

        Parameters
        ----------
        bias : numpy.array
            Current value for the biases
        grad : numpy.array
            Current value for the greadients

        Returns
        -------
        b : numpy.array
            The updated biases
        """
        if self.s_bias is None:
            self.s_bias = np.zeros(bias.shape)
        self.s_bias = self.gamma * self.s_bias + (1 - self.gamma) * (grad**2)
        lr = self.lr / np.sqrt(self.s_bias + EPS)
        self._save_one_learning_rate(lr, type='bias')
        b = bias - lr*grad
        return b

    def _save_one_learning_rate(self, lr, type='weights'):
        """ Stores one lr in the learning_rates accumulator.

        Parameters
        ----------
        lr : numpy.array
            Learning rates that have just been computed
        type : str
            Either 'weights' or 'bias', indicates whether the
            learning rates in lr refer to the weights of to the biases

        """
        lr = lr.ravel()
        self.learning_rates[type].append(lr[self.reference_index])

class Adam():
    """ Implements the Adam optimizer.

    Note that this algorithm uses leaky averaging to obtain an estimate
    both of the momentum and of the second moment of the gradient and
    stores them in two accumulators. The leaky averaging uses two different
    paramteres, b1 and b2. Then this two state vectors get normalized and
    used to compute the parameters' update.

    Parameters
    ----------
    learning_rate : float, optional
        The initial learning rate to use for the parameter's updates
    b1 : float, optional
        Parameter for the leaky averaging used to estimate the momentum
    b2 : float, optional
        Parameter for the leaky averaging used to estimate the second moment

    Arguments
    ---------
    lr : float
        The initial learning rate to use for the parameter's updates
    b1 : float
        Parameter for the leaky averaging used to estimate the momentum
    b2 : float
        Parameter for the leaky averaging used to estimate the second moment
    time_step: int
        Number of epochs run until now
    v_W : numpy.array
        Accumulator for the momentum of the weights gradient
    v_bias : numpy.array
        Accumulator for the moemntum of the weights biases
    s_W : numpy.array
        Accumulator for the second moment of the weights gradient
    s_bias : numpy.array
        Accumulator for the second moment of the bias gradient
    reference_index : int
        Index of the neuron for which we want to save the learning rates
    learning_rates : dict
        Accumulator where the learning rates for the neuron specified by
        reference_index are stored at each update
    """
    def __init__(self, learning_rate=0.01, b1=0.9, b2=0.99):

        self.lr = learning_rate
        self.b1 = b1
        self.b2 = b2
        self.time_step = 1
        self.v_W = None
        self.v_bias = None
        self.s_W = None
        self.s_bias = None
        self.reference_index = 18
        self.learning_rates = {
            'weights': [],
            'bias': []
        }

    def update_weights(self, weights, grad):
        """ Updates the weights given the gradient.

        The update rule is:
        W = W - lr * v_hat / (sqrt(s_hat + EPS))
        where EPS is just a small factor needed as to make sure
        that we never divide by zero and v_hat and s_hat are the
        regularized first and second momentum for the weights.

        Parameters
        ----------
        weights : numpy.array
            Current value for the weights
        grad : numpy.array
            Current value for the greadients

        Returns
        -------
        W : numpy.array
            The updated weights

        """
        if self.s_W is None:
            self.v_W = np.zeros(weights.shape)
            self.s_W = np.zeros(weights.shape)

        self.v_W = self.b1 * self.v_W + (1- self.b1) * grad
        self.s_W = self.b2 * self.s_W + (1 - self.b2) * (grad**2)

        v_hat = self.v_W / (1 - self.b1 ** self.time_step)
        s_hat = self.s_W / (1 - self.b2 ** self.time_step)

        lr = self.lr / (np.sqrt(s_hat) + EPS)
        self._save_one_learning_rate(lr)
        return weights - lr * v_hat

    def update_bias(self, bias, grad):
        """ Updates the biases given the gradient.

        The update rule is:
        W = W - lr * v_hat / (sqrt(s_hat + EPS))
        where EPS is just a small factor needed as to make sure
        that we never divide by zero and v_hat and s_hat are the
        regularized first and second momentum for the biases.

        Parameters
        ----------
        weights : numpy.array
            Current value for the biases
        grad : numpy.array
            Current value for the greadients

        Returns
        -------
        W : numpy.array
            The updated biases

        """
        if self.s_bias is None:
                self.v_bias = np.zeros(bias.shape)
                self.s_bias = np.zeros(bias.shape)

        self.v_bias = self.b1 * self.v_bias + (1- self.b1) * grad
        self.s_bias = self.b2 * self.s_bias + (1 - self.b2) * (grad**2)

        v_hat = self.v_bias / (1 - self.b1 ** self.time_step)
        s_hat = self.s_bias / (1 - self.b2 ** self.time_step)

        lr = self.lr / (np.sqrt(s_hat) + EPS)
        self._save_one_learning_rate(lr, type='bias')
        self.time_step +=1
        return bias - lr * v_hat

    def _save_one_learning_rate(self, lr, type='weights'):
        """ Stores one lr in the learning_rates accumulator.

        Parameters
        ----------
        lr : numpy.array
            Learning rates that have just been computed
        type : str
            Either 'weights' or 'bias', indicates whether the
            learning rates in lr refer to the weights of to the biases

        """
        lr = lr.ravel()
        self.learning_rates[type].append(lr[self.reference_index])


import matplotlib.pyplot as plt
import numpy as np

def f1(x):
    return 2+np.cos(x)+0.5*np.cos(2*x-0.5)
    #return  np.power(x,3)-3*np.power(x,2)+x-2

def Gf1(x):
    return -np.sin(x)-np.sin(2*x-0.5)
    #return  3*np.power(x,2)-6*x+1

def GradientDescent(x,Nm,eg,et,alpha,epsilon1,l):
    """ Gradient Descent Algorithm """
    xp    = np.zeros(Nm)
    xp[0] = x
    alphak = alpha
    for t in xrange(0,Nm-1):
        alphak = linear_search(xp[t],alphak,epsilon1,l)
        xp[t+1]=xp[t]-alphak*Gf1(xp[t])
        if np.abs(Gf1(xp[t+1]))<eg:
            print "Converged on critical point"
            return xp,t+1
        if np.abs(xp[t+1]-xp[t])<et:
            print "Converged on an x value"
            return xp,t+1
        if f1(xp[t])<f1(xp[t+1]):
            print "Diverging"
            return xp,t+1
    print "Maximum number of iterations reached"
    return xp, Nm

def linear_search(x,alpha,epsilon1,l):
    """Linear Search of the best alpha"""
    ak = alpha - epsilon1
    bk = alpha + epsilon1
    while bk-ak > l:
        lk = ((ak+bk)/2+ak)/2
        mk = ((ak+bk)/2+bk)/2

        if f1(x-lk*Gf1(x))< f1(x-mk*Gf1(x)):
            bk = (ak+bk)/2
        else:
            ak = (ak+bk)/2
    return (ak+bk)/2

if __name__ == '__main__':
  #Initial Values
  x     = 0.0
  Nm    = 500
  eg    = 0.001
  et    = 0.001
  alpha = 0.001
  delta = 0.001
  epsilon1 = 0.01
  l        = 0.00001

  xp, t = GradientDescent(x,Nm,eg,et,alpha,epsilon1,l)

  xr = np.arange(-6, 8, delta)

  y = f1(xr)
  yg = f1(xp)

  fig = plt.figure()

  plt.plot(xr,y,color='blue', linewidth=2)
  plt.plot(xp[:t],yg[:t],'ro')

  plt.grid()

  plt.show()

Machine_Learning_Algorithms/Gradient Descent with Adaptive Linear Search/GradientDesacent.py


import numpy as np

def rmse(deviation):  # root mean square error
    rmse = np.sqrt(((deviation) ** 2).mean())
    return rmse

class MetricRegression():
    def __init__(self, real, predict):
        self.real = np.array(real)
        self.predict = np.array(predict)
        self.samples_error = self.predict - self.real

    def MAE(self):
        mean_abs_error = np.mean(np.abs(self.predict - self.real))
        return mean_abs_error

    def STD(self):  # standard deviation
        std = np.std(self.samples_error, ddof=1)
        # print("standard deviation: %.4f" % std)
        return std

    def RMSE(self):
        RMSE_value = rmse(np.array(self.samples_error))
        return RMSE_value

    def MAP_error(self):  # Mean Absolute Percentage Error, MAPE
        abs_errors = np.abs(self.samples_error)
        ape_errors = abs_errors / self.real
        mean_ape = np.mean(ape_errors)
        return mean_ape

    def PPMC(self):  # Pearson Product Moment Correlation, PPMC
        predict_mean = np.mean(self.real)
        real_mean = np.mean(self.predict)

        numerator = np.sum((self.predict - predict_mean) * (self.real - real_mean))
        denominator = np.sqrt(np.sum(np.square(self.predict - predict_mean))) * np.sqrt(
            np.sum(np.square(self.real - real_mean)))
        PPMC = numerator / denominator
        return PPMC

    def Mape(self):  # mean APE
        abs_errors = np.abs(self.samples_error)
        ape_errors = abs_errors / self.real
        mean_ape = np.mean(ape_errors)
        return mean_ape


import networkx as nx
import numpy as np

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

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

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

    INPUT:

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

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

    '''
    G=nx.DiGraph()

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

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

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

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

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

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

    return G


import numpy as np
import matplotlib as plt
import math

# Domain parameters
Nx = 512
Lx = 6.
dx = Lx / Nx

# Creating the spatial computational grid:
x = np.arange(dx/2, Lx, dx) - Lx/2

# Physical parameters
H0 = 1.
g0 = 9.81
Eta_B = 0.00005*(np.exp( - (2*x / Lx)**2 )) * np.ones(Nx)    # Bottom topography

# Gravity wave speed
c0 = np.sqrt(g0 * H0)

# Spatial Differentiation function (second-order)
def ddx(f):

    xp1 = np.roll(f, -1)
    xm1 = np.roll(f,  1)

    d = (xp1 - xm1) / (2*dx)

    return d

# Spatial Differentiation function (forth-order)
def ddx4(f):

    xp1 = np.roll(f, -1)
    xp2 = np.roll(f, -2)
    xm1 = np.roll(f,  1)
    xm2 = np.roll(f,  2)

    d4 = ( -1/12 * xp2 + 2/3 * xp1 - 2/3 * xm1 + 1/12 * xm2) / (dx)
    # These coefficients are based on the output of Coefficients.py
    return d4

### Constrain temporal grid:
# Initial conditions

# CASE a
u_init = np.zeros(Nx)
h_init = H0 +   1e-2 * np.exp( - (x / 0.2)**2 ) - Eta_B
#h_init = H0* np.ones(Nx) - Eta_B

# CASE b
# pi=math.pi
# u_init =   3e-2 * np.cos( 4*pi*x/Lx) + 1e-1 * np.exp( - (x / 0.2)**2 )
# h_init = H0 *np.ones(Nx) - Eta_B

# Time-stepping cfl factor
cfl = 0.2

# Target end-time for the simulation
final_time = 3 * (Lx/2) / c0

# Initial time
time0 = 0.

# State how frequently we want to store outputs
out_freq = final_time / 200
Nouts    = int(final_time / out_freq) + 1
out_ind  = 1
# The times for the stored outputs
times = np.arange(Nouts) * out_freq

# An array to store the spatio-temporal evolution
simulated_h = np.zeros((Nouts, Nx))
simulated_u = np.zeros((Nouts, Nx))
# Store the initial conditions
simulated_u[0,:] = u_init
simulated_h[0,:] = h_init

# Initialize some working variables
time = time0
u = u_init.copy()
h = h_init.copy()
# Loop through time until we get to the target date
cnt = 0

# Discretization Schemes
# Temporal scheme order (Choose between 1, 2 or 3)
t_order = 3
# Spatial scheme order (Choose between 2 or 4)
s_order = 4

# Storing dudt and dhdt in an array
dudt = np.zeros((Nx,t_order))
dhdt = np.zeros((Nx,t_order))

while time < final_time:

    # Get dt
    dt = cfl * dx / (np.max(np.abs(u)) + c0)

    if s_order == 2:
        dudt[:,0] = - u * ddx(u) - g0 * ddx(h+Eta_B)
        dhdt[:,0] = - ddx(u * h)
    elif s_order == 4:
        dudt[:,0] = - u * ddx4(u) - g0 * ddx4(h+Eta_B)
        dhdt[:,0] = - ddx4(u * h)

    # Update fields (1st-order temporal)
    if t_order == 1:
        # Compute RHS of evolution equations and save
        dt_now = dt
        u += dudt[:,0] * dt_now
        h += dhdt[:,0] * dt_now
    if t_order == 2:
        #Update fields (2nd-order temporal)
        if cnt == 0:
            dt_now = dt/10.
            u += dudt[:,0] * dt_now
            h += dhdt[:,0] * dt_now
        else:
            dt_now = dt
            u += ( 1.5 * dudt[:,0] - 0.5 * dudt[:,-1] ) * dt_now
            h += ( 1.5 * dhdt[:,0] - 0.5 * dhdt[:,-1])  * dt_now
    if t_order == 3:
        #Update fields (3rd-order temporal)
        if cnt == 0:
            dt_now = dt/10.
            u += dudt[:,0] * dt_now
            h += dhdt[:,0] * dt_now
        if cnt == 1:
            dt_now = dt/10.
            u += ( 1.5 * dudt[:,0] - 0.5 * dudt[:,-1] ) * dt_now
            h += ( 1.5 * dhdt[:,0] - 0.5 * dhdt[:,-1])  * dt_now
        else:
            dt_now = dt
            u += ( 5/3 * dudt[:,0] - 5/6 * dudt[:,-1] + 1/6 * dudt[:,-2] ) * dt_now
            h += ( 5/3 * dhdt[:,0] - 5/6 * dhdt[:,-1] + 1/6 * dhdt[:,-2])  * dt_now

    # Update the history of dudt and dhdt values
    dudt = np.roll(dudt, -1, axis=1)
    dhdt = np.roll(dhdt, -1, axis=1)

    # Update time
    time += dt_now
    # u_vec[cnt] = u[cnt]
    # h_vec[cnt] = h[cnt]
    cnt += 1

    # Output, if appropraite
    if (time >= out_ind * out_freq):
        simulated_h[out_ind,:] = h
        simulated_u[out_ind,:] = u

        if (out_ind % 10 == 0):
            print('Stored output {0:d} of {1:d}.'.format(out_ind, Nouts-1))

        out_ind += 1

np.savez('simulation_results.npz',
         times = times,
         x = x,
         simulated_h = simulated_h,
         simulated_u = simulated_u,
         Eta_B = Eta_B,
         H0 = H0,
         g0 = g0,
         Lx = Lx)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Jan 28 09:13:01 2019

@author: gcu
"""

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

# This is just how xlog(x) looks like.
fig, ax = plt.subplots()
x = np.arange(0.01, 2.0, 0.01)
ax.plot(x, x*np.log10(x))
ax.grid(True, which='both')
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')

# Lets define a gaussian probl dist.
# Att. mean, std
x = np.arange(-4.0, 4.0, 0.01)
fig, ax = plt.subplots()
ft=scipy.stats.norm(0, 1)
ax.plot(x, ft.pdf(x))
ax.grid(True, which='both')
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')

# Now that we have the normal prob. distribution
# Let's plot its entropy as
# h(p) = -sum(pi*log(pi))

y_tbl = [ -1*y for y in ft.pdf(x)* np.log10(ft.pdf(x))]

fig, ax = plt.subplots()
x = np.arange(-4.0, 4.0, 0.01)
ax.plot(x,y_tbl, c='red')
ax.plot(x, ft.pdf(x), c='blue')
ax.grid(True, which='both')
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')

y_tbl = [ y for y in np.log10(ft.pdf(x))]

fig, ax = plt.subplots()
x = np.arange(-4.0, 4.0, 0.01)
ax.plot(x,y_tbl, c='red')
ax.plot(x, ft.pdf(x), c='blue')
ax.grid(True, which='both')
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')


# AUTOGENERATED! DO NOT EDIT! File to edit: 10_matrix_multiply.ipynb (unless otherwise specified).

__all__ = ['dotp', 'mmult']

# Cell
def dotp(v1, v2):
    "Get dot product of 2 vectors"
    sum = 0
    for i in range(0, len(v1)):
        sum += v1[i] * v2[i]
    return sum

# Cell
def mmult(m1, m2):
    "Get product of 2 matrices using [dotp](/mmult#dotp) "
    import numpy as np
    assert m1.shape[1] == m2.shape[0]
    vsize = m1.shape[1]
    pmatrix = np.zeros((m1.shape[0],m2.shape[1]))
    for i in range(0,m1.shape[0]):
        for j in range(0,m2.shape[1]):
            nv = dotp(m1[i,:], m2[:,j])
            pmatrix[i,j] = nv
    return pmatrix


import numpy as np
import sys

training_data_path = sys.argv[1]
testing_data_path = sys.argv[2]
output_path = sys.argv[3]

folds = 10
lambdas = [0,0.01,0.05,0.1,1,10,100,1000]

def train_model(X,Y,lam = 0):
    n = X.shape[0]
    X_t = np.transpose(X)
    XT_X = np.matmul(X_t,X)
    XT_X_inverse = np.linalg.inv(XT_X + (lam * np.identity(X.shape[1])))
    W = (XT_X_inverse).dot(X_t).dot(Y)
    return W

def predict_using_model(W,test_data):
    return (np.matmul(test_data,W))

def test_model(W,test_data,Y):
    Predictions = predict_using_model(W,test_data)
    return np.sum(np.square(Y - Predictions))

def kFold_cross_validation(X,Y,lambdas,folds):

    fold_size = int(X.shape[0]/folds)

    sums = []

    CV_test_X, CV_train_X = np.split(X.copy(), [fold_size], axis=0)
    CV_test_Y, CV_train_Y = np.split(Y.copy(), [fold_size], axis=0)

    for lam in lambdas:
        W= train_model(CV_train_X,CV_train_Y,lam)
        sums.append(test_model(W,CV_test_X,CV_test_Y))

    for i in range (0,folds-1):
        if i==folds-2:
            CV_train_X[i*fold_size:(i+1)*fold_size],CV_test_X = CV_test_X, CV_train_X[i*fold_size:]
            CV_train_Y[i*fold_size:(i+1)*fold_size],CV_test_Y = CV_test_Y, CV_train_Y[i*fold_size:]
        else:
            CV_train_X[i*fold_size:(i+1)*fold_size],CV_test_X = CV_test_X, CV_train_X[i*fold_size:(i+1)*fold_size]
            CV_train_Y[i*fold_size:(i+1)*fold_size],CV_test_Y = CV_test_Y, CV_train_Y[i*fold_size:(i+1)*fold_size]

        for i in range(0, len(lambdas)):
            W= train_model(CV_train_X,CV_train_Y,lam)
            sums[i] += test_model(W,CV_test_X,CV_test_Y)

    for i in range(0,len(sums)):
        sums[i] /= folds
    return sums

input_data = np.loadtxt(open(training_data_path, "rb"), delimiter=",")

Y = input_data[:,0].copy()
X = input_data.copy()
X[:,0] = 1

errors = kFold_cross_validation(X,Y,lambdas,folds)

min = errors[0]
lam = lambdas[0]

for i in range (0,len(lambdas)):
    if (errors[i] < min):
        lam = lambdas[i]
        min = errors[i]

W = train_model(X,Y,lam)

test_data = np.loadtxt(open(testing_data_path, "rb"), delimiter=",")

Y_test = test_data[:,0].copy()
X_test = test_data.copy()
X_test[:,0] = 1

Result = predict_using_model(W,X_test)

np.savetxt(output_path,Result)


import numpy as np
n=100
p = 2
primes = [True for i in range(n+1)] # All numbers are primes! At the beggining, at least!
while(p*p <= n): #Search to the root of n
    if primes[p] == True: # if the current number is still marked as a prime, it is a prime
        for multiple in range(p * 2, n+1, p): # we get all of its multiples
            primes[multiple] = False # And its multiples are not prime by definition
    p+=1

# We print all the primes!
primes_list = list()
print()
for p in range(2, n):
        if primes[p] == True:
            primes_list.append(p)
            print(p, end=', ')
print()

a = np.array(primes)
print(np.sum(a))
print(primes_list)

# print("El numero más grande de los primos es: ", a)
# while(True):
#     n = int(input("Number to investigate\n"))
#     if n in primes_list:
#         print("Sí!")
#     else:
#         print("No!")


# import packages: numpy, math (you might need pi for gaussian functions)
import numpy as np
import math
import matplotlib.pyplot as plt
from scipy.signal import convolve2d as conv2

"""
Gaussian function taking as argument the standard deviation sigma
The filter should be defined for all integer values x in the range [-3sigma,3sigma]
The function should return the Gaussian values Gx computed at the indexes x
"""
def gauss(sigma):

    #...

    return Gx, x

"""
Implement a 2D Gaussian filter, leveraging the previous gauss.
Implement the filter from scratch or leverage the convolve2D method (scipy.signal)
Leverage the separability of Gaussian filtering
Input: image, sigma (standard deviation)
Output: smoothed image
"""
def gaussianfilter(img, sigma):

    #...

    return smooth_img

"""
Gaussian derivative function taking as argument the standard deviation sigma
The filter should be defined for all integer values x in the range [-3sigma,3sigma]
The function should return the Gaussian derivative values Dx computed at the indexes x
"""
def gaussdx(sigma):

    #...

    return Dx, x

def gaussderiv(img, sigma):

    #...

    return imgDx, imgDy


#IIR Analog and Digital Butterworth BSF filter design
from scipy import signal
import numpy as np
import matplotlib.pyplot as plt
from pylab import clf
#Analog Band stop  butterworth filter, order =3, fc1=100Hz, fc2=300Hz
#b = numerator coefficients, a = denominator coefficients
#w = frequency variable, h= frequency response
b,a = signal.iirfilter(3,[100,300],btype ='bandstop',analog=True, ftype ='butter')
w,h = signal.freqs(b,a,1000)

fig1 = plt.figure(1)
ax = fig1.add_subplot(1,1,1)
ax.semilogx(w,20*np.log10(np.maximum(abs(h),1e-5)))
ax.set_title('Butterworth IIR Analog BSF frequency response')
ax.set_xlabel('Frequency in Hz---->')
ax.set_ylabel('Amplitude in dB----->')
#ax.axis((10,1000,-100,10))
ax.grid(which='both',axis='both')
plt.show()

#Digital Band stop butterworth filter, order =3, fc1=100Hz, fc2=300Hz
#SOS = second order digital filter sections
fs =1000
sos = signal.iirfilter(3,[100/fs,300/fs],btype ='bandstop',analog=False, ftype ='butter',output='sos')
w,h = signal.sosfreqz(sos,1000)

fig2 = plt.figure(2)
clf()
ax = fig2.add_subplot(1,1,1)
ax.plot(w/np.pi,20*np.log10(np.maximum(abs(h),1e-5)))
ax.set_title('Butterworth IIR Digital BSF frequency response')
ax.set_xlabel('Normalized Digital Frequency ---->')
ax.set_ylabel('Amplitude in dB----->')
#ax.axis((10,1000,-100,10))
ax.grid(which='both',axis='both')
plt.show()

#Result
##>>> b
##array([1.0e+00, 0.0e+00, 9.0e+04, 0.0e+00, 2.7e+09, 0.0e+00, 2.7e+13])
##>>> a
##array([1.0e+00, 4.0e+02, 1.7e+05, 3.2e+07, 5.1e+09, 3.6e+11, 2.7e+13])
##>>> sos
##array([[ 0.52762438, -0.89764821,  0.52762438,  1.        , -1.28407904,
##         0.50952545],
##       [ 1.        , -1.70130162,  1.        ,  1.        , -1.0263941 ,
##         0.65079467],
##       [ 1.        , -1.70130162,  1.        ,  1.        , -1.73866033,
##         0.83854915]])


import matplotlib.pyplot as plt
import numpy as np

def N_opt(k: float, r_m: float, p: float) -> float:
    """Calculates the number of months between each investment.

    Args:
      k: capital.
      r_m: monthly return.
      p: brokerage as fixed value.

    Returns:
      Montly frequency.
    """
    return np.ceil((p + p / r_m) / k)

def k_min(r_m: float, p: float) -> float:
    """Calculates minimum required capital
    for return to outweigh brokerage.

    Args:
      k: capital.
      p: brokerage as fixed value.

    Returns:
      Minimum capital.
    """

    return p + p / r_m

plt.rc("font", size=12)
plt.rc("axes", titlesize=12)
plt.rc("axes", labelsize=12)
plt.rc("xtick", labelsize=12)
plt.rc("ytick", labelsize=12)
plt.rc("legend", fontsize=12)
plt.rc("figure", titlesize=12)

fig, ax1 = plt.subplots(1, 1, figsize=(6, 4))

ks = np.linspace(65, 540, 10000)
N5 = np.zeros(len(ks))
N7 = np.zeros(len(ks))
N9 = np.zeros(len(ks))
for j, k_ in enumerate(ks):
    N5[j] = N_opt(k_, (1 + 0.05) ** (1 / 12) - 1, 2)
    N7[j] = N_opt(k_, (1 + 0.07) ** (1 / 12) - 1, 2)
    N9[j] = N_opt(k_, (1 + 0.09) ** (1 / 12) - 1, 2)
ax1.plot(ks, N9, label="CAGR = 9%", linewidth=3)
ax1.plot(ks, N7, label="CAGR = 7%", linewidth=3)
ax1.plot(ks, N5, label="CAGR = 5%", linewidth=3)

ax1.set_xlabel("Monthly capital, Euro")
ax1.set_ylabel(r"$N_\mathrm{opt}$, months")
ax1.legend()
ax1.grid(which="minor")
ax1.grid(which="major")

plt.tight_layout()
plt.savefig("brokerage_vs_oppertunity_cost.svg")

fig, ax1 = plt.subplots(1, 1, figsize=(6, 4))

r_y = np.linspace(0.02, 0.2, 10000)
r = (1 + r_y) ** (1 / 12) - 1
ax1.plot(r_y * 100, k_min(r, 2), linewidth=3)

ax1.set_ylabel("Minimum capitial, Euro")
ax1.set_xlabel(r"CAGR, %")
ax1.grid(which="minor")
ax1.grid(which="major")

plt.tight_layout()
plt.savefig("capital_vs_return.svg")


# Poisson disc sampling in arbitrary dimensions via Bridson algorithm
# Implementation by Pavel Zun, pavel.zun@gmail.com
# BSD licence - https://github.com/diregoblin/poisson_disc_sampling

# -----------------------------------------------------------------------------
# Based on 2D sampling by Nicolas P. Rougier - https://github.com/rougier/numpy-book
# -----------------------------------------------------------------------------

import numpy as np
from scipy.special import gammainc

# Uniform sampling in a hyperspere
# Based on Matlab implementation by Roger Stafford
# Can be optimized for Bridson algorithm by excluding all points within the r/2 sphere
def hypersphere_volume_sample(center,radius,k=1):
    ndim = center.size
    x = np.random.normal(size=(k, ndim))
    ssq = np.sum(x**2,axis=1)
    fr = radius*gammainc(ndim/2,ssq/2)**(1/ndim)/np.sqrt(ssq)
    frtiled = np.tile(fr.reshape(k,1),(1,ndim))
    p = center + np.multiply(x,frtiled)
    return p

# Uniform sampling on the sphere's surface
def hypersphere_surface_sample(center,radius,k=1):
    ndim = center.size
    vec = np.random.standard_normal(size=(k, ndim))
    vec /= np.linalg.norm(vec, axis=1)[:,None]
    p = center + np.multiply(vec, radius)
    return p

def squared_distance(p0, p1):
    return np.sum(np.square(p0-p1))

def Bridson_sampling(num_samples=20, dims=np.array([1.0,1.0]), radius=0.05, k=30, hypersphere_sample=hypersphere_volume_sample):
    # References: Fast Poisson Disk Sampling in Arbitrary Dimensions
    #             Robert Bridson, SIGGRAPH, 2007

    ndim=dims.size

    # size of the sphere from which the samples are drawn relative to the size of a disc (radius)
    sample_factor = 2
    if hypersphere_sample == hypersphere_volume_sample:
        sample_factor = 2

    # for the surface sampler, all new points are almost exactly 1 radius away from at least one existing sample
    # eps to avoid rejection
    if hypersphere_sample == hypersphere_surface_sample:
        eps = 0.001
        sample_factor = 1 + eps

    def in_limits(p):
        return np.all(np.zeros(ndim) <= p) and np.all(p < dims)

    # Check if there are samples closer than "squared_radius" to the candidate "p"
    def in_neighborhood(p, n=2):
        indices = (p / cellsize).astype(int)
        indmin = np.maximum(indices - n, np.zeros(ndim, dtype=int))
        indmax = np.minimum(indices + n + 1, gridsize)

        # Check if the center cell is empty
        if not np.isnan(P[tuple(indices)][0]):
            return True
        a = []
        for i in range(ndim):
            a.append(slice(indmin[i], indmax[i]))
        with np.errstate(invalid='ignore'):
            if np.any(np.sum(np.square(p - P[tuple(a)]), axis=ndim) < squared_radius):
                return True

    def add_point(p):
        points.append(p)
        indices = (p/cellsize).astype(int)
        P[tuple(indices)] = p

    cellsize = radius/np.sqrt(ndim)
    gridsize = (np.ceil(dims/cellsize)).astype(int)

    # Squared radius because we'll compare squared distance
    squared_radius = radius*radius

    # Positions of cells
    P = np.empty(np.append(gridsize, ndim), dtype=np.float32) #n-dim value for each grid cell
    # Initialise empty cells with NaNs
    P.fill(np.nan)

    points = []
    add_point(np.array([dims[0]/2, dims[1]/2]))
    while len(points):
        if len(P[~np.isnan(P).any(axis=ndim)]) > num_samples:
            break
        i = np.random.randint(len(points))
        p = points[i]
        del points[i]
        Q = hypersphere_sample(np.array(p), radius * sample_factor, k)
        for q in Q:
            if in_limits(q) and not in_neighborhood(q):
                add_point(q)
                if len(P[~np.isnan(P).any(axis=ndim)]) > num_samples:
                    return P[~np.isnan(P).any(axis=ndim)]
    return P[~np.isnan(P).any(axis=ndim)]


import numpy as np

THRESHOLD = 0.001

def get_amp(x):
    return abs(x)

# TODO: correction
def get_phase(x):
    return - np.arctan2(np.imag(x), np.real(x))

def fft(x):
    x = np.asarray(x, dtype=float)
    N = x.shape[0]

    if N == 1:
        return x
    elif N % 2 > 0:
        raise ValueError("Size of x must be a power of 2")
    else:
        X_even = fft(x[::2])
        X_odd = fft(x[1::2])
        factor = np.exp(-2j * np.pi * np.arange(N) / N)
        result = np.concatenate([X_even + factor[:N // 2] * X_odd,
                                 X_even + factor[N // 2:] * X_odd])
        return result

def get_spectrum_from_fft(fft_result, N):
    amplitudes = [get_amp(x)* 2 / N for x in fft_result]
    phases = [get_phase(x)  if amplitudes[i] > THRESHOLD else 0 for i, x in enumerate(fft_result)]
    return list(zip(amplitudes, phases))

def get_fft_spectrum(signal):
    fft_result = fft(signal)
    return get_spectrum_from_fft(fft_result, len(signal))


import numpy as np
import matplotlib.pyplot as plt
import cv2
from scipy import fftpack,signal
import scipy.signal
import skimage.measure

class PSFManager:

    def CreatePSF(self,name:str, **kwargs):
        choice = name.lower()
        if choice == 'sinc':
            return self.__sinc__(**kwargs)
        elif choice == 'gaussian':
            return self.__gaussian_kernel__(**kwargs)
        elif choice == 'box':
            return self.__box__(**kwargs)
        else:
            raise Exception(f"invalid name {name}")

    def ApplyPSF(self,image,psf):
        if psf is None or image is None:
             raise Exception(f"invalid parames")
        psf_image = signal.convolve2d(image, psf, mode='same', boundary='wrap')
        psnr = skimage.metrics.peak_signal_noise_ratio(image, psf_image)
        return (psf_image,psnr)

    def __sinc__(self,window_size):
        edge = window_size // 2
        x = np.linspace(-edge, edge, num=window_size)
        xx = np.outer(x, x)
        s = np.sinc(xx)
        s = s / s.sum()
        return s

    def __box__(self,width,height,box_size):
        h_ = int(height / 2)
        w_ = int(width / 2)
        d_ = int(box_size / 2)
        PSF_box_ = np.zeros((height, width))
        PSF_box_[h_ - d_:h_ + d_, w_ - d_:w_ + d_] = 1 / (box_size ** 2)
        return PSF_box_

    def __gaussian_kernel__(self,size, std=1):
        edge = size // 2
        ax = np.linspace(-edge, edge, num=size)
        xx, yy = np.meshgrid(ax, ax)
        kernel = np.exp(-(xx ** 2 + yy ** 2) / (2 * std **2))
        return kernel / kernel.sum()


import numpy as np

# Write a function that takes as input two lists Y, P,
# and returns the float corresponding to their cross-entropy.
def cross_entropy(Y, P):
    Y = np.array(Y)
    P = np.array(P) # predictions
    return -np.sum(Y * np.log(P) + (1-Y) * np.log(1-P))

if __name__=="__main__":
    Y = [1,0,1,1] # ground truth
    P = [0.4, 0.6, 0.1, 0.5] # predictions
    output = cross_entropy(Y, P)
    print(output)


import math
import numpy as np

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, p):
        return isinstance(p, Point) and self.x == p.x and self.y == p.y

    def to_numpy(self):
        return np.array([self.x, self.y], dtype=np.int32)

class Segment:
    def __init__(self, start_point, end_point):
        self.start = start_point
        self.end = end_point

    def x_len(self):
        return self.end.x - self.start.x

    def y_len(self):
        return self.end.y - self.start.y

    def len(self):
        return math.sqrt(self.x_len() ** 2 + self.y_len() ** 2)

    def ang(self):
        return math.atan2(self.end.y - self.start.y, self.end.x - self.start.x)

    def ang_deg(self):
        return self.ang() * (180 / math.pi)

    def rev_ang(self):
        return math.atan2(self.start.y - self.end.y, self.start.x - self.end.x)

    def rev_ang_deg(self):
        return self.rev_ang() * (180 / math.pi)

    def distance2point(self, p):
        A = p.x - self.start.x
        B = p.y - self.start.y
        C = self.x_len()
        D = self.y_len()

        len_sq = C**2 + D**2
        param = -1
        if len_sq != 0:
            param = A * C + B * D / len_sq

        if param < 0:
            xx = self.start.x
            yy = self.start.y
        elif param > 1:
            xx = self.end.x
            yy = self.end.y
        else:
            xx = self.start.x + param * C
            yy = self.start.y + param * D

        dx = p.x - xx
        dy = p.y - yy
        return math.sqrt(dx**2 + dy**2)

    def to_numpy(self):
        return np.array([
            self.start.to_numpy(),
            self.end.to_numpy()
        ], dtype=np.int32)

class Cutout:
    def __init__(self, side_segment, first_segment, second_segment):
        self.side_segment = side_segment
        self.first_segment = first_segment
        self.second_segment = second_segment

    def points(self):
        return [
            self.side_segment.start,
            self.first_segment.end,
            self.side_segment.end,
            self.side_segment.start
        ]

    def to_numpy(self):
        segments = [
            self.side_segment,
            self.first_segment,
            self.second_segment
        ]
        return segments_to_numpy_points(segments)

def segments_to_numpy_points(segments):
        segments = np.array([seg.to_numpy() for seg in segments])
        c, s, p = segments.shape
        points = segments.reshape((c * s, p))
        points = unique_rows(points)
        return np.array(points)

def unique_rows(a):
    a = np.ascontiguousarray(a)
    unique_a = np.unique(a.view([('', a.dtype)]*a.shape[1]))
    return unique_a.view(a.dtype).reshape((unique_a.shape[0], a.shape[1]))


import numpy as np
import matplotlib.pyplot as plt

N = 100
x = np.linspace(0, 1, N)
m, c = 5.5, 15.
err = 1.
y = m * x + c + np.random.randn(N)*err
yerr = np.ones_like(y)*err

def fit_line(x, y, yerr):
    AT = np.vstack((np.ones(len(x)), x))
    C = np.eye(len(x))*yerr
    CA = np.linalg.solve(C, AT.T)
    Cy = np.linalg.solve(C, y)
    ATCA = np.dot(AT, CA)
    ATCy = np.dot(AT, Cy)
    w = np.linalg.solve(ATCA, ATCy)

    cov = np.linalg.inv(ATCA)
    sig = np.sqrt(np.diag(cov))
    return w, sig

w, sig = fit_line(x, y, yerr)
print(f"{w[0]:.2f}, +/- {sig[0]:.1f}, {w[1]:.2f}, +/- {sig[1]:.1f}")

plt.errorbar(x, y, yerr=yerr, fmt="k.", ms=1, lw=.5, alpha=.5)
plt.fill_between(x, x*(w[1]+sig[1])+(w[0]+sig[0]),
                 x*(w[1]-sig[1])+(w[0]-sig[0]), alpha=.5)
plt.savefig("test")

# p, cov = np.polyfit(x, y, 1, cov=True)
# plt.plot(x, np.polyval(p, x), "C1", lw=3)
# plt.plot(x, np.polyval([m, c], x), "C4")

# m, c = p
# merr, cerr = np.sqrt(np.diag(cov))


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

# Example 1: Impulse
x1 = np.arange(0, 7)
x1_dense = np.linspace(start=0, stop=6, num=1000)
y1 = np.array([0, 0, 0, 1, 0, 0, 0])
f_cubic_splines = interpolate.interp1d(x1, y1, kind='cubic', fill_value='extrapolate')
f_akima = interpolate.Akima1DInterpolator(x1, y1)
f_pchip = interpolate.PchipInterpolator(x1, y1)

plt.figure(figsize=(6, 4))
plt.plot(x1, y1, label='Data', linestyle='None', marker='x')
plt.plot(x1_dense, f_cubic_splines(x1_dense), label='Cubic Splines')
plt.plot(x1_dense, f_akima(x1_dense), label='Akima')
plt.plot(x1_dense, f_pchip(x1_dense), label='Pchip')
plt.legend()
plt.savefig('interpolation-impulse.png', bbox_inches='tight')

# Example 2: Sampled Sine
x2 = np.linspace(start=0, stop=5.5, num=4)
x2_dense = np.linspace(start=0, stop=5.5, num=1000)
y2 = np.sin(x2)
f_cubic_splines = interpolate.interp1d(x2, y2, kind='cubic', fill_value='extrapolate')
f_akima = interpolate.Akima1DInterpolator(x2, y2)
f_pchip = interpolate.PchipInterpolator(x2, y2)

plt.figure(figsize=(6, 4))
plt.plot(x2, y2, label='Data', linestyle='None', marker='x')
plt.plot(x2_dense, f_cubic_splines(x2_dense), label='Cubic Splines')
plt.plot(x2_dense, f_akima(x2_dense), label='Akima')
plt.plot(x2_dense, f_pchip(x2_dense), label='Pchip')
plt.legend()
plt.savefig('interpolation-sine.png', bbox_inches='tight')

plt.show()


import numpy as np
import matplotlib.pyplot as plt

# The ODEs
def lorenz(x, y, z, sigma, rho, beta):
    differentialX = sigma*(y - x)
    differentialY = rho*x - y - x*z
    differentialZ = x*y - beta*z
    return differentialX, differentialY, differentialZ

# Initializing the Matrics
differentialT = 0.01
numberSteps = 10000
X = np.empty(numberSteps + 1)
Y = np.empty(numberSteps + 1)
Z = np.empty(numberSteps + 1)

# Set  values
X[0], Y[0], Z[0] = (1, 0, 20)
sigma=10
rho=28
beta=8/3

# Predictor-Corrector
for i in range(numberSteps):
    differentialX, differentialY, differentialZ = lorenz(X[i], Y[i], Z[i], sigma, rho, beta)
    X[i + 1] = X[i] + (differentialX * (differentialT/2))
    Y[i + 1] = Y[i] + (differentialY * (differentialT/2))
    Z[i + 1] = Z[i] + (differentialZ * (differentialT/2))

    differentialXM, differentialYM, differentialZM = lorenz(X[i + 1], Y[i + 1], Z[i + 1], sigma, rho, beta)
    X[i + 1] = X[i] + (differentialXM * differentialT)
    Y[i + 1] = Y[i] + (differentialYM * differentialT)
    Z[i + 1] = Z[i] + (differentialZM * differentialT)

# 3D Plot
# fig = plt.figure()
# ax = fig.gca(projection='3d')
# ax.plot(X,Y,Z, lw=0.5)
# ax.set_xlabel("X Axis")
# ax.set_ylabel("Y Axis")
# ax.set_zlabel("Z Axis")
# ax.set_title("Lorenz Attractor for Predictor-Corrector Method")
# plt.show()

# 2D Plot
fig, ((ax1, ax2)) = plt.subplots(1, 2)
fig.suptitle('Predictor Corrector Method for Lorez Equations')
ax1.plot(X,Z,lw=0.5)
ax1.set_title('Z(X)')
ax1.set_xlabel('X')
ax1.set_ylabel('Z')
ax2.plot(Y,Z,lw=0.5)
ax2.set_title('Z(Y)')
ax2.set_xlabel('Y')
ax2.set_ylabel('Z')
plt.show()


import numpy as np
import matplotlib.pyplot as plt

# the ODEs
def lorenz(x, y, z, sigma, rho, beta):
    differentialX = sigma*(y - x)
    differentialY = rho*x - y - x*z
    differentialZ = x*y - beta*z
    return differentialX, differentialY, differentialZ

# Initializing the Matrics
differentialT = 0.01
numberSteps = 10000
X = np.empty(numberSteps + 1)
Y = np.empty(numberSteps + 1)
Z = np.empty(numberSteps + 1)

# Set  values
X[0], Y[0], Z[0] = (1, 0, 20)
sigma=10
rho=28
beta=8/3

# Predictor-Corrector
for i in range(numberSteps):
    differentialX, differentialY, differentialZ = lorenz(X[i], Y[i], Z[i], sigma, rho, beta)
    X[i + 1] = X[i] + (differentialX * differentialT)
    Y[i + 1] = Y[i] + (differentialY * differentialT)
    Z[i + 1] = Z[i] + (differentialZ * differentialT)

# 3D Plot
# fig = plt.figure()
# ax = fig.gca(projection='3d')
# ax.plot(X, Y, Z, lw=0.5)
# ax.set_xlabel("X Axis")
# ax.set_ylabel("Y Axis")
# ax.set_zlabel("Z Axis")
# ax.set_title("Lorenz Attractor")
# plt.show()

# 2D Plot
fig, (ax1, ax2) = plt.subplots(2)
fig.suptitle('Lorenz Equations')
ax1.plot(X, Z, lw=0.5)
ax1.set_title('Euler Forward Method Z(X)')
ax1.set_xlabel('X')
ax1.set_ylabel('Z')
ax2.plot(Y, Z, lw=0.5)
ax2.set_title('Euler Forward Method Z(Y)')
ax2.set_xlabel('Y')
plt.show()


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

# Basic parameters
nt = 1000    # number of time steps
vs = 3000    # acoustic velocity
ro0 = 2500   # Density
isnap = 250  # snapshot frequency
nx = 1000    # number of grid points
isx = 500    # source location
xmax = 10000.
eps = 0.5    # stability limit

dx = xmax/(nx-1)  # calculate space increment
x = np.arange(0, nx)*dx   # initialize space coordinates
x = x.T

h = np.diff(x)  # Element sizes

# parameters
ro = x*0 + ro0
mu = x*0 + ro*vs**2

# time step from stabiity criterion
dt = 0.5*eps*dx/np.max(np.sqrt(mu/ro))

# source time function
pt = 20*dt
t = np.arange(1, nt+1)*dt  # initialize time axis
t0 = 3*pt
src = -1/pt**2*(t-t0)*np.exp(-1/pt**2*(t-t0)**2)

# Source vector
f = np.zeros(nx); f[isx:isx+1] = f[isx:isx+1] + 1.

# Stiffness matrix Kij
K = np.zeros((nx,nx))
for i in range(1, nx-1):
    for j in range(1, nx-1):
        if i==j:
            K[i,j] = mu[i-1]/h[i-1] + mu[i]/h[i]
        elif i==j+1:
            K[i,j] = -mu[i-1]/h[i-1]
        elif i+1==j:
            K[i,j] = -mu[i]/h[i]
        else:
            K[i,j] = 0
# Corner element
K[0,0] = mu[0]/h[0]
K[nx-1,nx-1] = mu[nx-1]/h[nx-2]

#%% CODE 10: Listing 6.4 Mass matrix with varying element size - Pag 147
# Mass matrix M_ij
M = np.zeros((nx,nx))
for i in range(1, nx-1):
    for j in range (1, nx-1):
        if j==i:
            M[i,j] = (ro[i-1]*h[i-1] + ro[i]*h[i])/3
        elif j==i+1:
            M[i,j] = ro[i]*h[i]/6
        elif j==i-1:
            M[i,j] = ro[i-1]*h[i-1]/6
        else:
            M[i,j] = 0
# Corner element
M[0,0] = ro[0]*h[0]/3
M[nx-1,nx-1] = ro[nx-1]*h[nx-2]/3
# Invert M
Minv = np.linalg.inv(M)

# Initialize FD matrices for comparison in the regular grid case
Mf = np.zeros((nx,nx), dtype=float)
D = np.zeros((nx,nx), dtype=float)
dx = h[1]

for i in range(nx):
    Mf[i,i] = 1./ro[i]
    if i>0:
        if i<nx-1:
            D[i+1,i] =1
            D[i-1,i] =1
            D[i,i] = -2

D = ro0*vs**2*D/dx**2

# Initialize fields
u = np.zeros(nx)
uold = np.zeros(nx)
unew = np.zeros(nx)

U = np.zeros(nx)
Uold = np.zeros(nx)
Unew = np.zeros(nx)

fig = plt.figure(figsize=(14,8), dpi=80)
fig.suptitle("1D Elastic wave solution", fontsize=16)
iplot = 0
#%% CODE 09: Listing 6.3 Time extrapolation - Pag 147
#   CODE 11: Listing 6.5 1D elastic case _ Pag 148
# Time extrapolation
for it in range(nt):
    # Finite Difference Method
    Unew = (dt**2)*Mf @ (D @ U + f/dx*src[it]) + 2*U - Uold
    Uold, U = U, Unew

    # Finite Element Method
    unew = (dt**2)*Minv @ (f*src[it] - K @ u) + 2*u - uold
    uold, u = u, unew

    # Display both
    if np.mod(it+1, isnap) == 0:
        # extract window
        xc = 500*dx + it*dt*vs - 150
        xd = 300
        iplot += 1

        plt.subplot(4,1,iplot)
        L1 = plt.plot(x, u, label='FEM')
        L2 = plt.plot(x, U, label='FDM')
        plt.legend()
        plt.text(xc+1.5*xd, 0.00000002, '%d m' %(xc-500*dx))

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


"""
This module illustrates how to use SymPy to differentiate and integrate
a function that is not a simple polynomial
"""
import sympy

x = sympy.symbols('x')
f = (x**2 - 2*x)*sympy.exp(3 - x)

fp = sympy.simplify(sympy.diff(f))
fp2 = -(x**2 - 4*x + 2)*sympy.exp(3 - x)

sympy.simplify(fp2 - fp) == 0 # True

F = sympy.integrate(f, x) # -x**2*exp(3 - x)


import numpy as np
from scipy import stats
import statsmodels.tsa.stattools as ts

CORRELATION_FUNCTIONS = \
    dict(kendall=stats.kendalltau,
         spearman=stats.spearmanr,
         pearson=stats.pearsonr)

def covariance(x: np.ndarray, y: np.ndarray) -> float:
    """ Covariance between x and y
    """
    cov_xy = np.cov(x, y)[0][1]

    return cov_xy

def co_integration(x: np.ndarray, y: np.ndarray):
    """ Co-integration test between x and y
    """
    r, _, _ = ts.coint(x, y)

    return r

def correlation(x: np.ndarray,
                y: np.ndarray,
                method: str = "kendall"):
    """ Correlation between x and y
    """
    assert method in ["pearson", "spearman", "kendall"]

    corr, p_value = CORRELATION_FUNCTIONS[method](x, y)

    return corr, p_value

import numpy,from scipy,import statsmodels


import numpy as np
import numba
from numba import jit
import random

SQRT_2PI = np.sqrt(2 * np.pi)

# Serial version
@jit(nopython=True)
def generate_perlin_noise_2d(shape, res):
    def f(t):
        return 6*t**5 - 15*t**4 + 10*t**3

    delta = (res[0] / shape[0], res[1] / shape[1])
    d = (shape[0] // res[0], shape[1] // res[1])
    grid = np.mgrid[0:res[0]:delta[0],0:res[1]:delta[1]].transpose(1, 2, 0) % 1
    # Gradients
    angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1)
    gradients = np.dstack((np.cos(angles), np.sin(angles)))
    g00 = gradients[0:-1,0:-1].repeat(d[0], 0).repeat(d[1], 1)
    g10 = gradients[1:,0:-1].repeat(d[0], 0).repeat(d[1], 1)
    g01 = gradients[0:-1,1:].repeat(d[0], 0).repeat(d[1], 1)
    g11 = gradients[1:,1:].repeat(d[0], 0).repeat(d[1], 1)
    # Ramps
    n00 = np.sum(grid * g00, 2)
    n10 = np.sum(np.dstack((grid[:,:,0]-1, grid[:,:,1])) * g10, 2)
    n01 = np.sum(np.dstack((grid[:,:,0], grid[:,:,1]-1)) * g01, 2)
    n11 = np.sum(np.dstack((grid[:,:,0]-1, grid[:,:,1]-1)) * g11, 2)
    # Interpolation
    t = f(grid)
    n0 = n00*(1-t[:,:,0]) + t[:,:,0]*n10
    n1 = n01*(1-t[:,:,0]) + t[:,:,0]*n11
    return np.sqrt(2)*((1-t[:,:,1])*n0 + t[:,:,1]*n1)

def monte_carlo_pi_serial(nsamples):
    acc = 0
    for i in range(nsamples):
        x = random.random()
        y = random.random()
        if (x**2 + y**2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples

# Parallel version
@jit(nopython=True, parallel=True)
def monte_carlo_pi_parallel(nsamples):
    acc = 0
    # Only change is here
    for i in numba.prange(nsamples):
        x = random.random()
        y = random.random()
        if (x**2 + y**2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples

def gaussians(x, means, widths):
    '''Return the value of gaussian kernels.

    x - location of evaluation
    means - array of kernel means
    widths - array of kernel widths
    '''
    n = means.shape[0]
    result = np.exp( -0.5 * ((x - means) / widths)**2 ) / widths
    return result / SQRT_2PI / n


  #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: sourav
"""

import numpy as np
import copy

from . import CustomExceptions
from .Householder import Householder

#%%

def pad(mat,step):
    """
    Pads a matrix from the top left with ones on the diagonal and zeroes
    everywhere else.

    Parameters
    ----------
    mat : numpy.ndarray
        A two dimensional array of floats (or ints).
    step : int
        A positive integer denoting the number of layers of padding.

    Returns
    -------
    out: numpy.ndarray
        A array of floats (even if the input mat was in int), where mat
        has been padded by step number of layers from the top left, with ones
        on the diagonal and zero everywhere else.

    Examples
    --------
    >>> import numpy as np
    >>> from QRdecomp.main import pad
    >>> A = np.random.randint(10, size=(3,3))
    >>> A
    array([[8, 0, 3],
           [4, 5, 3],
           [7, 6, 6]])
    >>> pad(A,1)
    array([[1., 0., 0., 0.],
           [0., 8., 0., 3.],
           [0., 4., 5., 3.],
           [0., 7., 6., 6.]])
    >>> B = np.random.rand(2,2)
    array([[0.66249402, 0.54029319],
           [0.5604081 , 0.45140005]])
    >>> pad(B,3)
    array([[1.        , 0.        , 0.        , 0.        , 0.        ],
           [0.        , 1.        , 0.        , 0.        , 0.        ],
           [0.        , 0.        , 1.        , 0.        , 0.        ],
           [0.        , 0.        , 0.        , 0.66249402, 0.54029319],
           [0.        , 0.        , 0.        , 0.5604081 , 0.45140005]])

    """

    size = mat.shape[0]

    mat = np.pad(mat, [(step,0),(step,0)],mode='constant',constant_values=0)

    additive = np.pad(np.eye(step),[(0,size),(0,size)],mode='constant',constant_values=0)

    return mat + additive

#%%

def order10(flo):
    """
    Calculates the order of a (positive) floating point number in base 10.

    Parameters
    ----------
    flo : float
        A positive floating point number.

    Returns
    -------
    out: int
        A positive or negative integer denoting the order of the input float in
        base 10. Express the floating point number flo in scientific notation
        ie <+>x.yz...e<sign><abs exponent>, then the order of the number is
        10^<sign><abs exponent>. This function outputs <sign><abs exponent>.

    Examples
    --------
    >>> import numpy as np
    >>> from QRdecomp.main import order10
    >>> order10(0.1234)
    -1
    >>> order10(np.sqrt(2599))
    1
    >>> order10(0.000000000235)
    -10
    """

    return int(np.floor(np.log10(flo)))

#%%

class QRdecomposition:
    """
    Instantiates a class for the QR decomposition of a two dimensional array.

    Parameters
    ----------
    matrix : array_like
        An array (a numpy.ndarray or a list or any other object that's converted
        to a numpy.ndarray by numpy.array) of integers or floats.
    mode : {'complete','reduced'} optional
        If mode='complete' (default), a complete QR decomposition is obtained.
        If mode='reduced', we obtain a reduced QR decomposition which is distinct
        from the complete QR decomposition when the number of rows > number of
        columns in the input matrix.

    Raises
    ------
    'The mode is unrecognized, please choose a valid mode.'
        If mode not in {'complete','reduced'}.

    'Sorry, we can only work with a two dimensional matrix!'
        If the input matrix is not two dimensional.

    Returns
    -------
    out: class qrdecomposition_sourav.main.QRdecomposition
        The output is a QRdecomposition
        class instance with some (private) attributes and methods relevant to the
        QR decomposition of the input matrix.

    See Also
    --------
    numpy.linalg.qr: numpy provided inbuilt function for QR decomposition.

    Examples
    --------
    >>> import numpy as np
    >>> from QRdecomp import QRdecomposition
    >>> A=np.array([[2,-2,18],[2,1,0],[1,2,0]])
    >>> QRA = QRdecomposition(A)
    >>> type(QRA)
    __main__.QRdecomposition
    >>> B = [[2,3,7],[1,3,2],[7,9,0]]
    >>> QRB = QRdecomposition(B,'reduced')
    >>> type(QRB)
    __main__.QRdecomposition
    >>>QRB2 = QRdecomposition(B,'not in list')
    The mode is unrecognized, please choose a valid mode.
    >>> C = np.random.rand(4,3)
    >>> C
    array([[0.94736141, 0.33226513, 0.87538724],
           [0.17070941, 0.37403276, 0.75319867],
           [0.37496236, 0.10273167, 0.62695294],
           [0.01891008, 0.50151402, 0.96066435]])
    >>> QRC = QRdecomposition(C)
    >>> type(QRC)
    __main__.QRdecomposition
    >>> D = np.random.rand(3,2,4)
    >>> D
    array([[[0.82479318, 0.43394209, 0.43307473, 0.92062568],
            [0.92035304, 0.82966978, 0.6720225 , 0.82205618]],

           [[0.6071503 , 0.17213224, 0.11951942, 0.8517612 ],
            [0.45166216, 0.09570765, 0.90400416, 0.55215103]],

           [[0.39489499, 0.7689566 , 0.45105273, 0.73672948],
            [0.77310856, 0.32992669, 0.48690335, 0.24372514]]])
    >>> QRD = QRdecomposition(D)
    Sorry, we can only work with a two dimensional matrix!
    """

    def __init__(self, matrix, mode='complete'):
        try:
            if mode not in ['complete','reduced']:
                raise CustomExceptions.ModeUnrecognized

            arrayQ = np.array(matrix,dtype='float64')

            if arrayQ.ndim == 2:
                self.__array = arrayQ
                self.__mode = mode
            else:
                raise CustomExceptions.DimensionError

        except CustomExceptions.ModeUnrecognized:
            print('The mode is unrecognized, please choose a valid mode.')
            print()

        except CustomExceptions.DimensionError:
            print('Sorry, we can only work with a two dimensional matrix!')
            print()

    def QR(self):
        """
        A QRdecomposition class method to calculate the tuple Q, R for the input
        matrix.

        Raises
        ------
        'Dummy! The matrix is already upper triangular.'
            If the input matrix is already upper triangular.

        Returns
        -------
        Q : numpy.ndarray
            The orthonormal Q matrix in the QR decomposition of the input matrix.
            If the input is already upper triangular, Q should be the identity
            (as per conventions chosen for s). However we raise an exception in
            this case as nothing needs to be computed.

            If mode='complete' (default), Q is a square matrix with dimension
            equal to the number of rows in the input matrix.

            If mode='reduced', and the input matrix has dimensions r,c with r>c,
            then Q has dimensions r,c.

        R : numpy.ndarray
            The upper triangular R matrix in the QR decomposition of the input
            matrix. If the input is already upper triangular, the same should be
            returned as a numpy.ndarray of floats (we raise an exception, and
            avoid the case altogether, it's just used for choosing conventions').

            If mode='complete', R has the same dimensions as the input matrix.

            If mode='reduced', and r>c, then R is a square matrix with dimension c.

        See Also
        --------
        QRdecomp.Householder: module for the Householder transform used herein.
        QRdecomp.CustomExceptions: module for the custom exceptions raised.
        numpy.linalg.qr: numpy provided inbuilt function for QR decomposition.

        Examples
        --------
        >>> import numpy as np
        >>> from QRdecomp import QRdecomposition
        >>> M1=np.random.randint(5,size=(3,3))
        >>> M1
        array([[0, 4, 1],
               [4, 2, 0],
               [3, 2, 1]])
        >>> QRdecomposition(M1).QR()
        (array([[ 1.        ,  0.        ,  0.        ],
                [ 0.        , -0.70710678,  0.70710678],
                [ 0.        , -0.70710678, -0.70710678]]),
         array([[ 0.        ,  4.        ,  1.        ],
                [ 0.        , -2.82842712, -0.70710678],
                [ 0.        ,  0.        , -0.70710678]]))
        >>> M2=np.triu(np.random.rand(4,4))
        >>> M2
        array([[0.88852557, 0.26433006, 0.42867313, 0.15930436],
               [0.        , 0.79417449, 0.50949558, 0.65408098],
               [0.        , 0.        , 0.24425774, 0.38858496],
               [0.        , 0.        , 0.        , 0.54905233]])
        >>> inst2 = QRdecomposition(M2)
        >>> inst2.QR()
        Dummy! The matrix is already upper triangular.
        >>> M3 = np.random.randint(10,size=(5,3))
        >>> M3
        array([[6, 2, 8],
               [5, 2, 4],
               [8, 0, 3],
               [8, 9, 1],
               [6, 0, 6]])
        >>> inst3 = QRdecomposition(M3)
        >>> inst3.QR()
        (array([[ 0.4       ,  0.07184854,  0.72581846,  0.32919083,  0.4468319 ],
                [ 0.33333333,  0.01260501,  0.16432914, -0.92156162,  0.11158006],
                [ 0.53333333,  0.47394827, -0.60381223,  0.13169552,  0.33014491],
                [ 0.53333333, -0.80230871, -0.19781058,  0.13163795, -0.12409155],
                [ 0.4       ,  0.3554612 ,  0.20607101,  0.08766588, -0.81455311]]),
         array([[15.        ,  6.26666667,  9.06666667],
                [ 0.        , -7.0518713 ,  3.37751169],
                [ 0.        ,  0.        ,  5.69104299],
                [ 0.        ,  0.        ,  0.        ],
                [ 0.        ,  0.        ,  0.        ]]))
        >>> inst3red = QRdecomposition(M3,'reduced')
        >>> inst3red.QR()
        (array([[ 0.4       ,  0.07184854,  0.72581846],
                [ 0.33333333,  0.01260501,  0.16432914],
                [ 0.53333333,  0.47394827, -0.60381223],
                [ 0.53333333, -0.80230871, -0.19781058],
                [ 0.4       ,  0.3554612 ,  0.20607101]]),
         array([[15.        ,  6.26666667,  9.06666667],
                [ 0.        , -7.0518713 ,  3.37751169],
                [ 0.        ,  0.        ,  5.69104299]]))

        """

        if '_QRdecomposition__Q' in dir(self):
            return self.__Q, self.__R                   # saves us computation if this method has been called already for the given instance
        else:
            R = copy.deepcopy(self.__array)

            max_lower_triangle = np.amax(np.abs(np.tril(R,-1)))
            try:
                if max_lower_triangle == 0.:
                    raise CustomExceptions.Pointless
                else:
                    step = 0
                    size = min(R.shape)
                    r = R.shape[0]
                    c = R.shape[1]
                    Q = np.eye(r)

                    for step in range(size):
                        Rredu = R[step:,step:,]
                        Hredu = Householder(Rredu,r-step)

                        np.matmul(Hredu,Rredu, out=Rredu)

                        H = pad(Hredu,step)
                        np.matmul(Q,H , out=Q)

                        step +=1

                    if self.__mode=='complete':
                        self.__Q = Q
                        self.__R = np.triu(R)

                    elif self.__mode=='reduced':
                        self.__Q = Q[:,:c]
                        self.__R = np.triu(R[:c,:])

                    self.__Runchanged = R

                    return self.__Q, self.__R

            except CustomExceptions.Pointless:
                print('Dummy! The matrix is already upper triangular.')
                print()

    def Qmatrix(self):
        """
        A QRdecomposition class method to calculate Q from the input matrix.
        Calls the QR() method if it hasn't already been called.

        Raises
        ------
        'Dummy! The matrix is already upper triangular.'
            If the input matrix is already upper triangular.

        Returns
        -------
        Q : numpy.ndarray
            The orthonormal Q matrix in the QR decomposition of the input matrix.
            If the input is already upper triangular, Q should be the identity
            (as per conventions chosen for s). However we raise an exception in
            this case as nothing needs to be computed.

            If mode='complete' (default), Q is a square matrix with dimension
            equal to the number of rows in the input matrix.

            If mode='reduced', and the input matrix has dimensions r,c with r>c,
            then Q has dimensions r,c.

        See Also
        --------
        numpy.linalg.qr: numpy provided inbuilt function for QR decomposition.

        Examples
        --------
        >>> import numpy as np
        >>> from QRdecomp import QRdecomposition
        >>> M1=np.random.randint(5,size=(3,3))
        >>> M1
        array([[3, 4, 3],
               [4, 3, 3],
               [2, 3, 2]])
        >>> QRdecomposition(M1).Qmatrix()
        array([[ 0.55708601,  0.52062513,  0.64699664],
               [ 0.74278135, -0.66079343, -0.10783277],
               [ 0.37139068,  0.54064917, -0.75482941]])
        >>> M2=np.triu(np.random.rand(4,4))
        >>> M2
        array([[0.28004935, 0.03446024, 0.57372709, 0.41737163],
               [0.        , 0.80320048, 0.0364231 , 0.96655091],
               [0.        , 0.        , 0.63195612, 0.65311413],
               [0.        , 0.        , 0.        , 0.02293199]])
        >>> inst2 = QRdecomposition(M2)
        >>> inst2.Qmatrix()
        Dummy! The matrix is already upper triangular.
        >>> M3 = np.random.randint(10,size=(5,3))
        >>> M3
        array([[4, 8, 4],
               [5, 7, 5],
               [1, 8, 5],
               [1, 4, 5],
               [2, 7, 1]])
        >>> inst3 = QRdecomposition(M3)
        >>> inst3.Qmatrix()
        array([[ 0.58345997, -0.01117707, -0.13190749, -0.68891393,  0.40920357],
               [ 0.72932496,  0.38002024,  0.19436236,  0.31975827, -0.4285413 ],
               [ 0.14586499, -0.79077741,  0.20197241, -0.210073  , -0.51814185],
               [ 0.14586499, -0.26545531,  0.68096803,  0.33861348,  0.5743521 ],
               [ 0.29172998, -0.3995801 , -0.66356114,  0.51416194,  0.224841  ]])
        >>> inst3red = QRdecomposition(M3,'reduced')
        >>> inst3red.Qmatrix()
        array([[ 0.58345997, -0.01117707, -0.13190749],
               [ 0.72932496,  0.38002024,  0.19436236],
               [ 0.14586499, -0.79077741,  0.20197241],
               [ 0.14586499, -0.26545531,  0.68096803],
               [ 0.29172998, -0.3995801 , -0.66356114]])

        """
        if '_QRdecomposition__Q' in dir(self):
            return self.__Q
        else:
            self.QR()
            if '_QRdecomposition__Q' in dir(self):
                return self.__Q

    def Rmatrix(self):
        """
        A QRdecomposition class method to calculate R from the input matrix.
        Calls the QR() method if it hasn't already been called.

        Raises
        ------
        'Dummy! The matrix is already upper triangular.'
            If the input matrix is already upper triangular.

        Returns
        -------
        R : numpy.ndarray
            The upper triangular R matrix in the QR decomposition of the input
            matrix. If the input is already upper triangular, the same should be
            returned as a numpy.ndarray of floats (we raise an exception, and
            avoid the case altogether, it's just used for choosing conventions').

            If mode='complete', R has the same dimensions as the input matrix.

            If mode='reduced', and r>c, then R is a square matrix with dimension c.

        See Also
        --------
        numpy.linalg.qr: numpy provided inbuilt function for QR decomposition.

        Examples
        --------
        >>> import numpy as np
        >>> from QRdecomp import QRdecomposition
        >>> M1=np.random.randint(5,size=(3,3))
        >>> M1
        array([[3, 4, 3],
               [4, 3, 3],
               [2, 3, 2]])
        >>> QRdecomposition(M1).Rmatrix()
        array([[5.38516481, 5.57086015, 4.64238345],
               [0.        , 1.72206772, 0.66079343],
               [0.        , 0.        , 0.10783277]])
        >>> M2=np.triu(np.random.rand(4,4))
        >>> M2
        array([[0.28004935, 0.03446024, 0.57372709, 0.41737163],
               [0.        , 0.80320048, 0.0364231 , 0.96655091],
               [0.        , 0.        , 0.63195612, 0.65311413],
               [0.        , 0.        , 0.        , 0.02293199]])
        >>> inst2 = QRdecomposition(M2)
        >>> inst2.Rmatrix()
        Dummy! The matrix is already upper triangular.
        >>> M3 = np.random.randint(10,size=(5,3))
        >>> M3
        array([[4, 8, 4],
               [5, 7, 5],
               [1, 8, 5],
               [1, 4, 5],
               [2, 7, 1]])
        >>> inst3 = QRdecomposition(M3)
        >>> inst3.Rmatrix()
        array([[ 6.8556546 , 13.56544421,  7.73084455],
               [ 0.        , -7.6143761 , -3.82535078],
               [ 0.        ,  0.        ,  4.19532287],
               [ 0.        ,  0.        ,  0.        ],
               [ 0.        ,  0.        ,  0.        ]])
        >>> inst3red = QRdecomposition(M3,'reduced')
        >>> inst3red.Rmatrix()
        array([[ 6.8556546 , 13.56544421,  7.73084455],
               [ 0.        , -7.6143761 , -3.82535078],
               [ 0.        ,  0.        ,  4.19532287]])

        """
        if '_QRdecomposition__R' in dir(self):
            return self.__R
        else:
            self.QR()
            if '_QRdecomposition__R' in dir(self):
                return self.__R

    def FloatingPointErrorOrder(self):
        """
        Gives an estimate of the floating point error involved in the QR decomposition.

        Raises
        ------
        'You need to call the method QR() first.'
            If the QR decomposition of the input matrix hasn't been performed yet.

        Returns
        -------
        out : string 'The floating point error is at best of the order of 10^<error order>'
              or string 'There are no floating point errors in the lower triangular elements of R up to the max precision at 0.'

              The <error> in the first case is the order to which we neglect the
              floating point error in producing an upper triangular R, ie the
              lower triangular 0. elements of R are accurate upto <sign>x. 10^<error order>,
              where x is single digit.

              The second case is self-explanatory.

        Examples
        --------
        >>> M3 = np.random.randint(10,size=(5,3))
        >>> M3
        array([[4, 8, 4],
               [5, 7, 5],
               [1, 8, 5],
               [1, 4, 5],
               [2, 7, 1]])
        >>> inst3 = QRdecomposition(M3)
        >>> inst3.FloatingPointErrorOrder()
        'The floating point error is at best of the order of 10^-16'

        """
        try:
            if '_QRdecomposition__R' in dir(self):
                max_lower_triangle = np.amax(np.abs(np.tril(self.__Runchanged,-1)))
                if max_lower_triangle > 0.:
                    self.__fperror = order10(max_lower_triangle)
                    return 'The floating point error is at best of the order of 10^%d' %self.__fperror
                else:
                    return 'There are no floating point errors in the lower triangular elements of R up to the max precision at 0.'
            else:
                raise CustomExceptions.CallQR

        except CustomExceptions.CallQR:
            print('You need to call the method QR() first.')
            print()


# >>> import numpy as np
# >>> x = np.array([1.0, 2.0, 3.0])
# >>> print(x)
# >>> type(x)
# >>> x = np.array([1.0, 2.0, 3.0])
# >>> y = np.array([2.0, 4.0, 6.0])
# >>> x + y
# >>> x - y
# >>> x / y
# >>> x = np.array([1.0, 2.0, 3.0])
# >>> x / 2.0
# >>> A = np.array([[1, 2], [3, 4]])
# >>> print(A)
# >>> A.shape
# >>> A.dtype
# >>> B = np.array([[3, 0], [0, 6]])
# >>> A + B
# >>> A * B
# >>> print(A)
# >>> A * 10
# >>> A = np.array([[1, 2], [3, 4]])
# >>> B = np.array([10, 20])
# >>> A * B
# >>> X = np.array([[51, 55], [14, 19], [0, 4]])
# >>> print(X)
# >>> X[0]
# >>> X[0][1]
# >>> for row in X:
# ...     print(row)
# ...
# >>> X = X.flatten()
# >>> print(X)
# >>> X[np.array([0, 2, 4])]
# >>> X > 15
# >>> X[X>15]


import numpy as np

def softmax(x):
    """Compute the softmax function for each row of the input x.

    It is crucial that this function is optimized for speed because
    it will be used frequently in later code. You might find numpy
    functions np.exp, np.sum, np.reshape, np.max, and numpy
    broadcasting useful for this task.

    Numpy broadcasting documentation:
    http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html

    You should also make sure that your code works for a single
    N-dimensional vector (treat the vector as a single row) and
    for M x N matrices. This may be useful for testing later. Also,
    make sure that the dimensions of the output match the input.

    You must implement the optimization in problem 1(a) of the
    written assignment!

    Arguments:
    x -- A N dimensional vector or M x N dimensional numpy matrix.

    Return:
    x -- You are allowed to modify x in-place
    """
    orig_shape = x.shape

    if len(x.shape) > 1:
        # Matrix
        ### YOUR CODE HERE
        x = np.apply_along_axis(optimized_softmax,1,x)
        ### END YOUR CODE
    else:
        # Vector
        ### YOUR CODE HERE
        x = optimized_softmax(x)
        ### END YOUR CODE

    assert x.shape == orig_shape
    return x

def optimized_softmax(x):
        x = x-np.max(x)
        #print x
        exps = np.exp(x)
        #print exps
        x = exps/np.sum(exps)
        return x

def test_softmax_basic():
    """
    Some simple tests to get you started.
    Warning: these are not exhaustive.
    """
    print "Running basic tests..."
    test1 = softmax(np.array([1,2]))
    print test1
    ans1 = np.array([0.26894142,  0.73105858])
    assert np.allclose(test1, ans1, rtol=1e-05, atol=1e-06)

    test2 = softmax(np.array([[1001,1002],[3,4]]))
    print test2
    ans2 = np.array([
        [0.26894142, 0.73105858],
        [0.26894142, 0.73105858]])
    assert np.allclose(test2, ans2, rtol=1e-05, atol=1e-06)

    test3 = softmax(np.array([[-1001,-1002]]))
    print test3
    ans3 = np.array([0.73105858, 0.26894142])
    assert np.allclose(test3, ans3, rtol=1e-05, atol=1e-06)

    print "You should be able to verify these results by hand!\n"

def test_softmax():
    """
    Use this space to test your softmax implementation by running:
        python q1_softmax.py
    This function will not be called by the autograder, nor will
    your tests be graded.
    """
    print "Running your tests..."
    ### YOUR CODE HERE
    x = np.array([1,2])
    y = np.array([[100,2,3,4],[4,4,5,3]])
    test1 = softmax(x)
    test2 = softmax(y)
    print test1
    print test2
    ### END YOUR CODE

if __name__ == "__main__":
    test_softmax_basic()
    test_softmax()


#!/usr/bin/env python
# coding: utf-8

# this is for baylor code assignment.
# K means

import numpy as np

def init_clusters(k, points):
    return points[np.random.randint(points.shape[0], size=k)]

def cal_distances(centroid, points):
    return np.linalg.norm(points - centroid, axis=1)

def k_means(k, points, max_iter = 100):
    centroids = init_clusters(k, points)
    costs = np.zeros(max_iter, dtype=np.float64)
    classes = np.zeros(points.shape[0], dtype=np.float64)
    distances = np.zeros([points.shape[0], k], dtype=np.float64)
    for idx in range(max_iter):
        for i,c in enumerate(centroids):
            distances[:, i] = cal_distances(c, points)
        classes = np.nanargmin(distances, axis = 1)
        costs[idx] = np.sum(np.nanmin(distances, axis = 1) , dtype=np.float64) / points.shape[0]
        for c in range(k):
            centroids[c] = np.mean(points[classes == c], axis = 0)
    return classes, costs[-1], centroids, classes

def find_bestK(X, min_k = 2, max_k = 31):
    pre_improve = None
    pre_cost = None
    cur_cost = None
    improve = None
    costs = []
    ks = []
    for k in range(min_k, max_k):
        classes, cur_cost, centroids, classes = k_means(k, X, 10)
        ks.append(k)
        costs.append(cur_cost)
    return k, cur_cost, ks,costs

def shortest_distance(point, coef):
    return abs((coef[0]*point[0])-point[1]+coef[1])/ ((coef[0]*coef[0])+1)**0.5

def load_file(X):
    return np.loadtxt(X)

def set_data_dir(X):
    global data_dir
    data_dir = X if X.endswith('/') else X + '\\'

def set_file_name(X):
    global file_name
    file_name = X

def gen_new_file(X,k):
    tmp = X.split('.')
    name = tmp[0]
    out_file = name + '_' + str(k) + '.' + tmp[1]
    np.save(out_file, k)

def run(data_dir, file):
    # set_data_dir("../datasets/")
    # set_file_name("0.txt")
    cs = []
    ks = []
    full_file = data_dir + file
    data = load_file(full_file)
    k, c, ks, cs = find_bestK(data)
    co = np.polyfit((ks[0],ks[-1]),(cs[0],cs[-1]),1)
    best_k = None
    long_dis = float('-inf')
    for k,c in zip(ks,cs):
        d = shortest_distance((k,c),co)
        if d > long_dis:
            long_dis = d
            best_k = k
    print("Best k:" + str(best_k))
    gen_new_file(file,best_k)

# data_dir = '.'
# file_name = None

# run(data_dir, file_name)


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

sns.set()

class ModelOrderSelector():

    def __init__(self):
        self.method_values = None

    def apply(self, method, x, max_order, estimator):
        '''
            Applies method for model order selection by calculationg function
            value for each order in given interval.

            Args:
                method (string): Method. It can be 'FPE', 'AIC' and 'CAT'.
                x (array of doubles): Signal.
                max_order (integer): Maximum order for the method.
                estimator (parametric estimator): One of parametric estimators.
        '''
        if method is None or estimator is None:
            return
        if method == 'FPE' or method == 'AIC' or method == 'CAT':
            self.method = method
            self.max_order = max_order
            self.rho = np.zeros(max_order + 1)
            self.x = x
            self.N = len(x)
        else:
            return

        # Apply estimator.
        for k in np.arange(1, self.max_order + 1):
            print('Applying for k={}'.format(k))
            estimator.estimate(self.x, p=k)
            self.rho[k] = estimator['var_u']

        # Apply method.
        self.method_values = np.zeros(self.max_order + 1)
        for k in np.arange(1, self.max_order + 1):
            self.method_values[k] = self._apply_for_given_k(k, self.rho[k])

    def plot(self):
        '''
            Plots method values for order in interval [1, max_order].
        '''
        # Check if anything is calculated.
        if self.method is None or self.method_values is None or self.max_order is None:
            return

        # Plot all.
        plt.figure()
        plt.plot(np.arange(1, self.max_order + 1), self.method_values[1:])

        # Mark the optimal value and order.
        min_indx = np.argmin(self.method_values[1:])
        k_opt = min_indx + 1
        plt.plot(k_opt, self.method_values[k_opt], 'ro',
                 label='Min value for k_opt={}'.format(k_opt))

        plt.title('Criterion in model order selection')
        plt.xlabel('k')
        plt.ylabel('{}[k]'.format(self.method))
        plt.legend()
        plt.show()

    def _apply_for_given_k(self, k, rho_k):
        if self.method == 'FPE':
            return (self.N + k) / (self.N - k) * rho_k
        elif self.method == 'AIC':
            return self.N * math.log(rho_k) + 2 * k
        elif self.method == 'CAT':
            rho_tilda_k = self.N / (self.N - k) * rho_k

            if k == 1:
                self.rho_tilda_sum = 1 / rho_tilda_k
            else:
                self.rho_tilda_sum += 1 / rho_tilda_k

            return self.rho_tilda_sum / self.N - 1 / rho_tilda_k
        else:
            return None

    def __getitem__(self, key):
        if key == 'method':
            return self.method
        if key == 'values':
            return self.method_values
        return None


import numpy as np

def make_spirals(n_samples=100, shuffle=True):
    theta = np.sqrt(np.random.rand(n_samples)) * 2 * np.pi
    r_a = 2 * theta + np.pi
    data_a = np.array([np.cos(theta) * r_a, np.sin(theta) * r_a]).T
    x_a = data_a + np.random.randn(n_samples, 2)
    y_a = np.zeros(n_samples)

    r_b = -2 * theta - np.pi
    data_b = np.array([np.cos(theta) * r_b, np.sin(theta) * r_b]).T
    x_b = data_b + np.random.randn(n_samples, 2)
    y_b = np.ones(n_samples)

    x = np.concatenate((x_a, x_b))
    y = np.concatenate((y_a, y_b))
    if shuffle:
        p = np.random.permutation(len(y))
        x = x[p]
        y = y[p]
    return x, y.astype(int)

def make_checkerboard(n_samples=450, padding=0.01, shuffle=True):
    n_samples_per_square = n_samples // 9

    def g(_x_diff, _y_diff):
        a = np.random.rand(n_samples_per_square, 2)
        a[a < padding] = padding
        a[a > 1 - padding] = 1 - padding
        a[:, 0] += _x_diff
        a[:, 1] += _y_diff
        return a

    x_list = []
    y_list = []
    for x_diff in range(3):
        for y_diff in range(3):
            y_list.append(np.ones(n_samples_per_square) * ((x_diff + y_diff) % 2))
            x_list.append(g(x_diff, y_diff))

    x = np.concatenate(x_list)
    y = np.concatenate(y_list)
    if shuffle:
        p = np.random.permutation(len(y))
        x = x[p]
        y = y[p]
    return x, y