from dataclasses import dataclass

import numpy as np
from scipy.stats import norm

@dataclass
class option:
    S: np.ndarray
    K: np.ndarray
    T: np.ndarray
    v: np.ndarray
    r: np.ndarray
    q: np.ndarray

def __ds(o: option) -> tuple:
    denominator = o.v * np.sqrt(o.T)
    first_term  = np.log(o.S / o.K)
    second_term = o.r - o.q + o.v * o.v / 2

    d1 = (1 / denominator) * (first_term + second_term * o.T)
    d2 = d1 - denominator

    return d1, d2

def call_delta(o: option) -> np.ndarray:
    d1, d2 = __ds(o)
    deltas = np.exp(- o.q * o.T) * norm.cdf(d1)
    return deltas

def call_gamma(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    g1 = np.exp(- o.q * o.T) * norm.pdf(d1)
    g2 = o.S * o.v * np.sqrt(o.T)
    return g1 / g2

def call_theta(o: option) -> np.ndarray:
    d1, d2 = __ds(o)
    first_term  = - o.S * np.exp(-o.q * o.T) * norm.pdf(d1) * v / (2 * np.sqrt(o.T))
    second_term = - o.r * o.K * np.exp(-o.r * o.T) * norm.cdf(d2)
    third_term = o.q * o.S * np.exp(-o.q * o.T) * norm.cdf(d1)

    this_theta = first_term + second_term + third_term

    return this_theta / 100

def call_vega(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    this_vega = o.S * np.exp(-o.q * o.T) * norm.pdf(d1) * np.sqrt(o.T)
    return this_vega / 100

def call_rho(o: option) -> np.ndarray:
    _, d2 = __ds(o)
    return o.K * np.exp(-o.r * o.T) * norm.cdf(d2) / 100

def call_dollar_delta(Notional: float, o: option) -> np.ndarray:
    return Notional * call_delta(o)

def call_dollar_gamma(Notional: float, o: option) -> np.ndarray:
    return Notional * call_gamma(o) * o.S / 100

def call_dollar_theta(Notional: float, o: option) -> np.ndarray:
    return Notional * call_theta(o)

def call_dollar_vega(Notional: float, o: option) -> np.ndarray:
    return Notional * call_vega(o)

def call_dollar_rho(Notional: float, o: option) -> np.ndarray:
    return Notional * call_rho(o)

def put_delta(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    deltas = np.exp(- o.q * o.T) * (norm.cdf(d1) - 1)
    return deltas

def put_gamma(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    g1 = np.exp(- o.q * o.T) * norm.pdf(d1)
    g2 = o.S * o.v * np.sqrt(o.T)
    return g1 / g2

def put_theta(o: option) -> np.ndarray:
    d1, d2 = __ds(o)
    first_term  = - np.exp(o.q * o.T) * o.S * norm.pdf(d1) * o.v / (2 * np.sqrt(o.T))
    second_term = + o.r * o.K * np.exp(-o.r * o.T) * norm.cdf(-d2)
    third_term  = - o.q * o.S * np.exp(-o.q * o.T) * norm.cdf(-d1)

    this_theta = first_term + second_term + third_term

    return this_theta / 100

def put_vega(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    this_vega = o.S * np.exp(-o.q * o.T) * norm.pdf(d1) * np.sqrt(o.T)
    return this_vega / 100

def put_rho(o: option) -> np.ndarray:
    _, d2 = __ds(o)
    return - o.K * np.exp(-o.r * o.T) * norm.cdf(-d2) / 100

def put_dollar_delta(Notional: float, o: option) -> np.ndarray:
    return Notional * put_delta(o)

def put_dollar_gamma(Notional: float, o: option) -> np.ndarray:
    return Notional * put_gamma(o) * o.S / 100

def put_dollar_theta(Notional: float, o: option) -> np.ndarray:
    return Notional * put_theta(o)

def put_dollar_vega(Notional: float, o: option) -> np.ndarray:
    return Notional * put_vega(o)

def put_dollar_rho(Notional: float, o: option) -> np.ndarray:
    return Notional * put_rho(o)

"""
def __ds(S: np.ndarray, \
       K: np.ndarray, \
       T: np.ndarray, \
       v: np.ndarray, \
       r: np.ndarray, \
       q: np.ndarray) -> tuple:

    denominator = v * np.sqrt(T)
    first_term  = np.log(S / K)
    second_term = r - q + v * v / 2

    d1 = (1 / denominator) * (first_term + second_term * T)
    d2 = d1 - denominator

    return d1, d2

def call_delta(S: np.ndarray, \
               K: np.ndarray, \
               T: np.ndarray, \
               v: np.ndarray, \
               r: np.ndarray, \
               q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    deltas = np.exp(- q * T) * norm.cdf(d1)
    return deltas

def call_gamma(S: np.ndarray, \
               K: np.ndarray, \
               T: np.ndarray, \
               v: np.ndarray, \
               r: np.ndarray, \
               q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    g1 = np.exp(- q * T) * norm.pdf(d1)
    g2 = S * v * np.sqrt(T)
    return g1 / g2

def call_theta(S: np.ndarray, \
               K: np.ndarray, \
               T: np.ndarray, \
               v: np.ndarray, \
               r: np.ndarray, \
               q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    first_term  = - S * np.exp(-q * T) * norm.pdf(d1) * v / (2 * np.sqrt(T))
    second_term = - r * K * np.exp(-r * T) * norm.cdf(d2)
    third_term = q * S * np.exp(-q * T) * norm.cdf(d1)

    this_theta = first_term + second_term + third_term

    return this_theta / 100

def call_vega(S: np.ndarray, \
              K: np.ndarray, \
              T: np.ndarray, \
              v: np.ndarray, \
              r: np.ndarray, \
              q: np.ndarray) -> np.ndarray:

    d1, _ = __ds(S, K, T, v, r, q)
    this_vega = S * np.exp(-q * T) * norm.pdf(d1) * np.sqrt(T)
    return this_vega / 100

def call_rho(S: np.ndarray, \
             K: np.ndarray, \
             T: np.ndarray, \
             v: np.ndarray, \
             r: np.ndarray, \
             q: np.ndarray) -> np.ndarray:

    _, d2 = __ds(S, K, T, v, r, q)
    return K * np.exp(-r * T) * norm.cdf(d2) / 100

def call_dollar_delta(Notional: float, \
                      S: np.ndarray,   \
                      K: np.ndarray,   \
                      T: np.ndarray,   \
                      v: np.ndarray,   \
                      r: np.ndarray,   \
                      q: np.ndarray) -> np.ndarray:
    return Notional * call_delta(S, K, T, v, r, q)

def call_dollar_gamma(Notional: float, \
                      S: np.ndarray,   \
                      K: np.ndarray,   \
                      T: np.ndarray,   \
                      v: np.ndarray,   \
                      r: np.ndarray,   \
                      q: np.ndarray) -> np.ndarray:

    return Notional * call_gamma(S, K, T, v, r, q) * S  / 100

def call_dollar_theta(Notional: float, \
                      S: np.ndarray,   \
                      K: np.ndarray,   \
                      T: np.ndarray,   \
                      v: np.ndarray,   \
                      r: np.ndarray,   \
                      q: np.ndarray) -> np.ndarray:
    return Notional * call_theta(S, K, T, v, r, q)

def call_dollar_vega(Notional: float, \
                     S: np.ndarray,   \
                     K: np.ndarray,   \
                     T: np.ndarray,   \
                     v: np.ndarray,   \
                     r: np.ndarray,   \
                     q: np.ndarray) -> np.ndarray:
    return Notional * call_vega(S, K, T, v, r, q)

def call_dollar_rho(Notional: float,  \
                    S: np.ndarray,    \
                    K: np.ndarray,    \
                    T: np.ndarray,    \
                    v: np.ndarray,    \
                    r: np.ndarray,    \
                    q: np.ndarray) -> np.ndarray:
    return Notional * call_rho(S, K, T, v, r, q)

def put_delta(S: np.ndarray, \
              K: np.ndarray, \
              T: np.ndarray, \
              v: np.ndarray, \
              r: np.ndarray, \
              q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    deltas = np.exp(- q * T) * (norm.cdf(d1) - 1)
    return deltas

def put_gamma(S: np.ndarray, \
              K: np.ndarray, \
              T: np.ndarray, \
              v: np.ndarray, \
              r: np.ndarray, \
              q: np.ndarray) -> np.ndarray:

    d1, _ = __ds(S, K, T, v, r, q)
    g1 = np.exp(- q * T) * norm.pdf(d1)
    g2 = S * v * np.sqrt(T)
    return g1 / g2

def put_theta(S: np.ndarray, \
              K: np.ndarray, \
              T: np.ndarray, \
              v: np.ndarray, \
              r: np.ndarray, \
              q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    first_term  = - np.exp(q * T) * S * norm.pdf(d1) * v / (2 * np.sqrt(T))
    second_term = + r * K * np.exp(-r * T) * norm.cdf(-d2)
    third_term  = - q * S * np.exp(-q * T) * norm.cdf(-d1)

    this_theta = first_term + second_term + third_term

    return this_theta / 100

def put_vega(S: np.ndarray, \
             K: np.ndarray, \
             T: np.ndarray, \
             v: np.ndarray, \
             r: np.ndarray, \
             q: np.ndarray) -> np.ndarray:

    d1, _ = __ds(S, K, T, v, r, q)
    this_vega = S * np.exp(-q * T) * norm.pdf(d1) * np.sqrt(T)
    return this_vega / 100

def put_rho(S: np.ndarray, \
            K: np.ndarray, \
            T: np.ndarray, \
            v: np.ndarray, \
            r: np.ndarray, \
            q: np.ndarray) -> np.ndarray:
    _, d2 = __ds(S, K, T, v, r, q)
    return - K * np.exp(-r * T) * norm.cdf(-d2) / 100

def put_dollar_delta(Notional: float, \
                     S: np.ndarray,   \
                     K: np.ndarray,   \
                     T: np.ndarray,   \
                     v: np.ndarray,   \
                     r: np.ndarray,   \
                     q: np.ndarray) -> np.ndarray:
    return Notional * put_delta(S, K, T, v, r, q)

def put_dollar_gamma(Notional: float, \
                     S: np.ndarray,   \
                     K: np.ndarray,   \
                     T: np.ndarray,   \
                     v: np.ndarray,   \
                     r: np.ndarray,   \
                     q: np.ndarray) -> np.ndarray:
    return Notional * put_gamma(S, K, T, v, r, q) * S  / 100

def put_dollar_theta(Notional: float, \
                     S: np.ndarray,   \
                     K: np.ndarray,   \
                     T: np.ndarray,   \
                     v: np.ndarray,   \
                     r: np.ndarray,   \
                     q: np.ndarray) -> np.ndarray:
    return Notional * put_theta(S, K, T, v, r, q)

def put_dollar_vega(Notional: float, \
                    S: np.ndarray,   \
                    K: np.ndarray,   \
                    T: np.ndarray,   \
                    v: np.ndarray,   \
                    r: np.ndarray,   \
                    q: np.ndarray) -> np.ndarray:
    return Notional * put_vega(S, K, T, v, r, q)

def put_dollar_rho(Notional: float,  \
                   S: np.ndarray,    \
                   K: np.ndarray,    \
                   T: np.ndarray,    \
                   v: np.ndarray,    \
                   r: np.ndarray,    \
                   q: np.ndarray) -> np.ndarray:

    return Notional * put_rho(S, K, T, v, r, q)
"""


import numpy as np
from numpy import linalg as LA
import pandas as pd
import matplotlib.pyplot as plt
import os
from utils import covariance, LinearLeastSquare, TotalLeastSquare, RANSAC

np.set_printoptions(formatter={'all':lambda x: str(x)})
file =os.path.join("../data","insurance_data.csv")
# Read cvs data into a dataframe
data = pd.read_csv(file)
x = data['age'].to_numpy()
y = data['charges'].to_numpy()
# Perform min-max normalization to bring data to the same scale
x_n = (x - np.min(x))/(np.max(x) - np.min(x))
y_n = (y - np.min(y))/(np.max(y) - np.min(y))

# Q1 _____________________________________________________________
# Compute Covariance matrix of original & normalized data
cov_mat = np.array([[covariance(x, x), covariance(x, y)], [covariance(x, y), covariance(y, y)]])
print("Covariance Matrix of original data: ")
print(cov_mat)

cov_mat = np.array([[covariance(x_n, x_n), covariance(x_n, y_n)], [covariance(x_n, y_n), covariance(y_n, y_n)]])
print("Covariance Matrix of normalized data: ")
print(cov_mat)
eig_val, eig_vec = LA.eig(cov_mat)

eig_vec1 = eig_vec[:,np.argmax(eig_val)] # Eigen vector corresponding to max eigen value
eig_vec2 = eig_vec[:,np.argmin(eig_val)] # Eigen vector corresponding to max eigen value

print("Dot product of eigen vectors is: ", np.dot(eig_vec1, eig_vec2))

plt.figure(1)
plt.scatter(x, y)
plt.title("Principal Components")
plt.xlabel('age')
plt.ylabel('insurance_charges')

# Rescale each eigen vector to fit the graph and plot them at the mean of the data
plt.quiver(np.mean(x), np.mean(y), eig_vec1[0]*(np.max(x) - np.min(x))*np.max(eig_val) , eig_vec1[1]*(np.max(y) - np.min(y))*np.max(eig_val) , label = 'Principal Component 1', color = 'r', units='xy', angles='xy', scale_units='xy', scale=0.2)
plt.quiver(np.mean(x), np.mean(y), eig_vec2[0]*(np.max(x) - np.min(x))*np.min(eig_val) , eig_vec2[1]*(np.max(y) - np.min(y))*np.min(eig_val) , label = 'Principal Component 2', units='xy', angles='xy', scale_units='xy', scale =0.2)

plt.legend()
# __________________________________________________________________

# Q2________________________________________________________________

plt.figure(2)
plt.scatter(x, y)
plt.title("Line Fitting using LS & TLS")
plt.xlabel('age')
plt.ylabel('insurance_charges')
ls = LinearLeastSquare()
# Fit original data using LinearLeastSquare as it is scale invariant
m, c = ls.fit_line(x, y)
y1 = m*x + c
plt.plot(x, y1, label='LS', color='r')

# Fit normalized data using TotalLeastSquare
tls = TotalLeastSquare()
m, c = tls.fit_line(x_n, y_n)

x2 = np.linspace(np.min(x_n), np.max(x_n), x_n.size)
x_ps = np.linspace(np.min(x), np.max(x), x.size)

y2 = m*x2 + c
# Rescale data by performing inverse of min-max normalization
y2 = y2*(np.max(y) - np.min(y)) + np.min(y)
plt.plot(x_ps, y2, label="TLS", color='g')
plt.legend()

plt.figure(3)
# Fit normalized data using RANSAC
"""Optional : fit_line function of RANSAC class takes an optional boolean flag, "optimal_fit" while returns the slope and intercept after running Linear Least Square on only the inliers as a final step.

Set to False as default for comparison between LS, TLS & RANSAC
"""
ransac = RANSAC()
m, c, mask = ransac.fit_line(x_n, y_n, 0.001, p=0.99, optimal_fit = False)
# Plot outliers
plt.scatter(x[np.invert(mask)], y[np.invert(mask)], label='Outliers', color = 'tab:orange')
# Plot inliers
plt.scatter(x[mask], y[mask], label='Inliers', color='g')
plt.title("Line Fitting using RANSAC")
plt.xlabel('age')
plt.ylabel('insurance_charges')
y3 = m*x2 + c
# Rescale data by performing inverse of min-max normalization
y3 = y3*(np.max(y) - np.min(y)) + np.min(y)
plt.plot(x_ps, y3, color='r')
plt.legend()

plt.show()


import time
import math
import numpy as np
import torch

from log_uniform import LogUniformSampler

def log_uniform_sample(N, size):
    log_N = math.log(N)
    x = torch.Tensor(size).uniform_(0, 1)
    value = torch.exp(x * log_N).long() - 1
    return torch.remainder(value, N)

def log_uniform(class_id, range_max):
    return (math.log(class_id+2) - math.log(class_id+1)) / math.log(range_max+1)

def log_uniform_distribution(range_max):
    distribution = np.asarray([log_uniform(idx, range_max) for idx in range(range_max)])
    return torch.from_numpy(distribution)

N = 793471
num_samples = 8192
batch_size = 128 * 20

start_time = time.time()
log_uniform_sample(N, num_samples)
end_time = time.time()
print("non_unique log_uniform numpy", end_time - start_time)

p = log_uniform_distribution(N)
start_time = time.time()
torch.multinomial(p, num_samples, replacement=True)
end_time = time.time()
print("non_unique multinomial cuda", end_time - start_time)

start_time = time.time()
torch.multinomial(p, 100)
end_time = time.time()
print("unique multinomial cuda", end_time - start_time)

sampler = LogUniformSampler(N)
labels = np.random.choice(N, batch_size)

start_time = time.time()
sample_id, true_freq, sample_freq = sampler.sample(num_samples, labels)
end_time = time.time()
print("unique log_uniform c++", end_time - start_time)

"""
sampler = LogUniformSampler()
start_time = time.time()
sample_id = sampler.sample(N, num_samples, unique=True, labels=labels.tolist())
end_time = time.time()
print("unique no_accidental_hits log_uniform c++", end_time - start_time)

label_set = set(labels.tolist())
for idx in sample_id:
    assert(idx not in label_set)
"""


# title: Gradient descent
# author: Jiahong Ma
# data: 2020/11/24

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

def f(x):
    x1 = x[0]
    x2 = x[1]
    return math.exp(x1 + 3 * x2 - 0.1) + math.exp(x1 - 3 * x2 - 0.1) + math.exp(-x1 - 0.1)

def grad(x):
    x1 = x[0]
    x2 = x[1]
    gradx1 = math.exp(x1 + 3 * x2 - 0.1) * 1 + math.exp(x1 - 3 * x2 - 0.1) * 1 + math.exp(-x1 - 0.1) * -1
    gradx2 = math.exp(x1 + 3 * x2 - 0.1) * 3 + math.exp(x1 - 3 * x2 - 0.1) * -3
    return np.asarray([gradx1, gradx2])

def norm(grad):
    gradx1 = grad[0]
    gradx2 = grad[1]
    return (gradx1 ** 2 + gradx2 ** 2) ** 0.5

def gradient_descent(alpha, beta, epsilon, x0):
    assert (alpha > 0 and alpha < 0.5)
    assert (beta > 0 and beta < 1)

    x = [x0]
    d = [-1 * grad(x0)]
    k = 0

    while (norm(grad(x[k])) > epsilon):
        t = 1
        rawf = f(x[k] + t * d[k])
        newf = f(x[k]) + alpha * t * np.dot(grad(x[k]), d[k])
        while (rawf > newf):
            t = beta * t
            rawf = f(x[k] + t * d[k])
            newf = f(x[k]) + alpha * t * np.dot(grad(x[k]), d[k])

        newx = x[k] + t * d[k]
        x.append(newx)
        d.append(-1 * grad(newx))
        k = k + 1

    return x

def distance(result):
    dist = []
    for x in result:
        dist.append(f(x) - TRUTH)
    return dist

def alpha_exp(k):
    alpha = k
    betalist = [0.1,  0.3, 0.5, 0.7, 0.9]
    dist = []
    for x in betalist:
        tempresult = (gradient_descent(alpha = alpha, beta = x, epsilon = 0.35, x0 = np.asarray([0.45,0.45])))
        dist.append(distance(tempresult))
    return dist,betalist

def beta_exp(k):
    beta = k
    alphalist = [0.1, 0.2, 0.3, 0.4]
    dist = []
    for x in alphalist:
        tempresult = (gradient_descent(alpha = x, beta = beta, epsilon = 0.1, x0 = np.asarray([0.12,0.12])))
        dist.append(distance(tempresult))
    return dist,alphalist

#--------------------------------------------truth------------------------------------------
t = gradient_descent(alpha = 0.3, beta = 0.3, epsilon = 0.000000001, x0 = np.asarray([1,1]))
TRUTH = f(t[len(t)-1])

for x in t:
    print('delta:')
    print(f(x)-TRUTH)


# Create basic vector field.  This is a point cloud where each point
# has a vector and magnitude attached to it.
#
import pyvista
import numpy as np
x, y = np.meshgrid(np.linspace(-5,5,10),np.linspace(-5,5,10))
points = np.vstack((x.ravel(), y.ravel(), np.zeros(x.size))).T
u = x/np.sqrt(x**2 + y**2)
v = y/np.sqrt(x**2 + y**2)
vectors = np.vstack((u.ravel()**3, v.ravel()**3, np.zeros(u.size))).T
pdata = pyvista.vector_poly_data(points, vectors)
pdata.point_data.keys()
# Expected:
## ['vectors', 'mag']
#
# Convert these to arrows and plot it.
#
pdata.glyph(orient='vectors', scale='mag').plot()


#implements the LVQ algorithm

import numpy as np

#reading of data from files
def reading_data(string):
    with open(string, 'r') as f:
        data = f.readlines()

    all_data = []
    all_label = []

    for item in data:
        x = item.split(',')
        #obtaining label from data
        label = x[-1].rstrip()
        #obtaining features from data
        indv_data = x[:-1]
        indv_data = np.array(indv_data).astype(np.float)

        all_data.append(indv_data)

        #converting labels into one-hot vectors
        if label == 'Iris-setosa':
            label_vec = 0
            all_label.append(label_vec)
        elif label == 'Iris-versicolor':
            label_vec = 1
            all_label.append(label_vec)
        elif label == 'Iris-virginica':
            label_vec = 2
            all_label.append(label_vec)

    return (all_data, all_label)

#calculating euclidean distance
def euclidean_distance(input_vec, weights):
    distance = 0
    for i in range(len(weights)):
        distance += (input_vec[i] - weights[i])**2
    return np.sqrt(distance)

#shuffling of data and labels
def shuffle(data, labels):
    index = [i for i in range(len(data))]
    np.random.shuffle(index)
    data = np.array(data)
    labels = np.array(labels)
    return data[index], labels[index]

f = open("LVQ1.txt", "w")

all_data, all_label = reading_data('iris_train.txt')
all_data, all_label = shuffle(all_data, all_label)

test_data, test_label = reading_data('iris_test.txt')
test_data, test_label = shuffle(test_data, test_label)

#initialising weights and other parameters
np.random.seed(4)
weights = np.random.rand(3,4)
print(weights)
alpha = 0.3
n_epochs = 50

for epoch in range(n_epochs):
    cluster_error = np.zeros(3)
    lr = alpha * (1 - epoch/n_epochs)

    for i in range(len(all_data)):
        #calculating euclidean distance between data and each node
        distances = []
        for j in range(3):
            distances.append(euclidean_distance(all_data[i], weights[j]))
        #obtaining the predicted label
        predicted_label = int(distances.index(min(distances)))
        actual_label = int(all_label[i])

        #calculation of mean squared error
        error = all_data[i] - weights[predicted_label]
        mean_squared_error = np.dot(error, error)
        cluster_error[predicted_label] += mean_squared_error

        #performing weight change
        if (predicted_label == actual_label):
            delta_w = lr * error
        else:
            delta_w = -lr * error
        weights[predicted_label] += delta_w

        #weights normalisation
        weights[predicted_label] /= np.linalg.norm(weights[predicted_label])

    print("epoch:", epoch, "error:", cluster_error)
    f.write("epoch: " + str(epoch) + " error: " + str(cluster_error) + "\n")

print("weights", weights)
f.write("codebook vector 0: " + str(weights[0]) + "\n")
f.write("codebook vector 1: " + str(weights[1]) + "\n")
f.write("codebook vector 2: " + str(weights[2]) + "\n")

#calculation of confusion matrix
confusion_matrix = np.zeros([3,3])
correct_predictions = 0

#testing on test data
for i in range(len(test_data)):
    distances = []
    for j in range(3):
        distances.append(euclidean_distance(test_data[i], weights[j]))
    predicted_label = int(distances.index(min(distances)))
    actual_label = int(test_label[i])
    if (int(predicted_label) == int(actual_label)):
        correct_predictions += 1

    confusion_matrix[predicted_label][actual_label] += 1

print(confusion_matrix)
print("accuracy", correct_predictions/len(test_data))

f.close()


#The covariance matrix and its eigenvalues the hard way
from random import random, seed
import numpy as np

def covariance(x, y, n):
    sum = 0.0
    mean_x = np.mean(x)
    mean_y = np.mean(y)
    for i in range(0, n):
        sum += (x[(i)]-mean_x)*(y[i]-mean_y)
    return  sum/n

n = 10

x = np.random.normal(size=n)
y = np.random.normal(size=n)
z = x*x*x+y*y +0.5*np.random.normal(size=n)
covxx = covariance(x,x,n)
covxy = covariance(x,y,n)
covxz = covariance(x,z,n)
covyy = covariance(y,y,n)
covyz = covariance(y,z,n)
covzz = covariance(z,z,n)

SigmaCov = np.array([ [covxx, covxy, covxz], [covxy, covyy, covyz], [covxz, covyz, covzz]])
print(SigmaCov)

EigValues, EigVectors = np.linalg.eig(SigmaCov)
# sort eigenvectors and eigenvalues
permute = EigValues.argsort()
EigValues = EigValues[permute]
EigVectors = EigVectors[:,permute]
print(EigValues)
print(EigVectors)


import numpy as np

from skipi.function import Function
from skipi.fourier import FourierTransform, fourier_matrix, InverseFourierTransform, invfourier_matrix

from ..helper import assert_equal

def test_compare_matrix_with_fourier_function():
    x_space = np.linspace(-10, 10, 1000)
    fun = Function(x_space, lambda x: np.exp(-x ** 2 / 2))

    w_space = np.linspace(-10, 10, 1000)
    f1 = FourierTransform.from_function(w_space, fun)

    trafo = fourier_matrix(x_space, w_space)
    f2 = Function.to_function(w_space, np.dot(trafo, fun(x_space)))

    assert_equal(f1, f2, 1e-14)

def test_compare_matrix_with_invfourier_function():
    x_space = np.linspace(-10, 10, 1000)
    fun = Function(x_space, lambda x: np.exp(-x ** 2 / 2))

    w_space = np.linspace(-10, 10, 1000)
    f1 = InverseFourierTransform.from_function(w_space, fun)

    trafo = invfourier_matrix(x_space, w_space)
    f2 = Function.to_function(w_space, np.dot(trafo, fun(x_space)))

    assert_equal(f1, f2, 1e-15)

def test_compare_matrix_InverseFourier():
    x_space = np.linspace(-10, 10, 1000)
    w_space = np.linspace(-10, 10, 1000)
    fun = Function(x_space, lambda x: np.exp(-x ** 2 / 2))

    trafo = fourier_matrix(x_space, w_space)
    f = Function.to_function(w_space, np.dot(trafo, fun(x_space)))

    fun2 = InverseFourierTransform.from_function(x_space, f)

    assert_equal(fun, fun2, 1e-15)

def test_compare_InverseFourier_matrix():
    x_space = np.linspace(-10, 10, 1000)
    w_space = np.linspace(-10, 10, 1000)
    fun = Function(x_space, lambda x: np.exp(-x ** 2 / 2))

    f = FourierTransform.from_function(w_space, fun)

    trafo = invfourier_matrix(x_space, w_space)
    fun2 = Function.to_function(w_space, np.dot(trafo, f(x_space)))

    assert_equal(fun, fun2)


import tensorflow as tf
import numpy as np

xy = np.array([[828.659973, 833.450012, 908100, 828.349976, 831.659973],
               [823.02002, 828.070007, 1828100, 821.655029, 828.070007],
               [819.929993, 824.400024, 1438100, 818.97998, 824.159973],
               [816, 820.958984, 1008100, 815.48999, 819.23999],
               [819.359985, 823, 1188100, 818.469971, 818.97998],
               [819, 823, 1198100, 816, 820.450012],
               [811.700012, 815.25, 1098100, 809.780029, 813.669983],
               [809.51001, 816.659973, 1398100, 804.539978, 809.559998]])

def MinMaxScaler(data):
    numerator = data - np.min(data, 0)
    denominator = np.max(data, 0) - np.min(data, 0)
    # noise term prevents the zero division
    return numerator / (denominator + 1e-7)

xy = MinMaxScaler(xy)

x_data = xy[:, 0:-1]
y_data = xy[:, [-1]]

X = tf.placeholder(tf.float32, shape=[None, 4], name='x_data')
Y = tf.placeholder(tf.float32, shape=[None, 1], name='y_data')

W = tf.Variable(tf.random_normal([4, 1]), name='weight')
b = tf.Variable(tf.random_normal([1]), name='bias')

hypothesis = tf.matmul(X, W) + b
cost = tf.reduce_mean(tf.square(hypothesis - Y))

optimizer = tf.train.GradientDescentOptimizer(learning_rate=1e-5)
train = optimizer.minimize(cost)

sess = tf.Session()
sess.run(tf.global_variables_initializer())

for step in range(2001):
    cost_val, hy_val, _ = sess.run([cost, hypothesis, train], feed_dict={X: x_data, Y: y_data})
    print(step, 'Cost: ', cost_val, '\nPrediction: ', hy_val)


from math import floor, sqrt
from pandas import DataFrame as DF
from scipy import sparse

from anndata import AnnData

def digits(n, b, empty_zero=False, significant_leading_zeros=True):
    '''Convert a number to an array of base-`b` digits, with a few toggle-able
    behaviors.

    The default parameters output an analogue to "spreadsheet-column" order, e.g. in
    base-26 you get the equivalent of: "A","B",…,"Z","AA","AB",… (but with arrays of
    integers ∈[0,26) instead of strings of letters).

    :param empty_zero: when True, start counting from an empty array at zero
            (otherwise, zero will be mapped to [0])
    :param significant_leading_zeros: when True, enumerate non-empty natural-number
            arrays containing the elements ∈[0,b)
    '''
    if significant_leading_zeros:
        if not empty_zero: return digits(n+1, b, empty_zero=True, significant_leading_zeros=significant_leading_zeros)
        bases = [1]
        while n >= bases[-1]:
            n -= bases[-1]
            bases.append(bases[-1]*b)
        n_digits = digits(n, b, empty_zero=True, significant_leading_zeros=False)
        return [0]*(len(bases)-1-len(n_digits)) + n_digits
    else:
        return digits(n // b, b, empty_zero=True, significant_leading_zeros=False) + [n%b] if n else [] if empty_zero else [0]

def spreadsheet_column(idx):
    return ''.join([ chr(ord('A')+digit) for digit in digits(idx, 26) ])

def make_obs(r, R=None):
    if R is None:
        r, R = 0, r
    return DF([
        {
            'label': f'row {i}',
            'idx²': i**2,
            'Prime': i >= 2 and all([
                i % f != 0
                for f in range(2, floor(sqrt(i)))
            ]),
        }
        for i in range(r, R)
    ])

def make_var(c, C=None):
    if C is None:
        c, C = 0, c
    return DF([
        {
            'name': spreadsheet_column(i),
            'sqrt(idx)': sqrt(i),
        }
        for i in range(c, C)
    ])

def make_test_h5ad(R=100, C=200):
    if isinstance(R, int): R = (0, R)
    (r, R) = R
    if isinstance(C, int): C = (0, C)
    (c, C) = C
    M, N = R-r, C-c
    X = sparse.random(M, N, format="csc", density=0.1, random_state=123)
    obs = make_obs(r, R)
    var = make_var(c, C)
    ad = AnnData(X=X, obs=obs, var=var)
    return ad


# Importing the dependancies
import numpy as np
import matplotlib.pyplot as plt
from planar_utils import *
# Loading the data
X, y = load_data()

# Visualizing the data
plot_planar_data(X, y)

# Defining the sigmoid function
def sigmoid(z):
    return 1 / (1 + np.exp(-z))

# Defining the relu function
def relu(z):
    return z * (z > 0)

# Defining the derivative during back propagation
def relu_derivative(z):
    return 1 * (z > 0)

# Defining some global variables
hidden_neurons = 10
epochs = 10000
m = X.shape[1]
learning_rate = 1.2
cost = [0]*epochs # For visualizaion
epsilon = 1e-8

# Initializing the weights
W1 = np.random.randn(hidden_neurons, X.shape[0])*0.01
b1 = np.zeros((hidden_neurons, 1))
W2 = np.random.randn(y.shape[0], hidden_neurons)*0.01
b2 = np.zeros((y.shape[0], 1))

v_t = np.zeros((4,))

for epoch in range(epochs):

    # Forward propagation
    Z1 = np.dot(W1, X) + b1
    A1 = np.tanh(Z1)

    Z2 = np.dot(W2, A1) + b2
    A2 = sigmoid(Z2)

    # Cost
    logprobs = np.multiply(np.log(A2 + epsilon), y) + np.multiply((1 - y), np.log(1 - A2 + epsilon))

    cost[epoch] = - np.sum(logprobs) / m
    if epoch%1000 == 0: # Printing the cost after every 1000 iterations for debugging
        print(f'Cost after {epoch} iterations = {cost[epoch]}')

    # Backward propagation
    dZ2= A2 - y
    dW2 = (1/m) * np.dot(dZ2, A1.T)
    db2 = (1/m) * np.sum(dZ2, axis=1, keepdims=True)
    dZ1 = np.multiply(np.dot(W2.T, dZ2), 1-np.power(A1, 2))
    dW1 = (1/m) * np.dot(dZ1, X.T)
    db1 = (1/m) * np.sum(dZ1, axis=1, keepdims=True)

    grads = np.array([dW2, db2, dW1, db1])
    # caculating the exponential average.
    v_t = 0.8*v_t + learning_rate*grads
    # Updating the parameters
    W2 -= v_t[0]
    b2 -= v_t[1]
    W1 -= v_t[2]
    b1 -= v_t[3]

plt.style.use('fivethirtyeight')

# Visualizing the cost function
plt.plot(cost, label = 'cost')
plt.xlabel('Epochs')
plt.ylabel('Cost')
plt.legend()
plt.show()

# Predicting
Z1 = np.dot(W1, X) + b1
A1 = np.tanh(Z1)

Z2 = np.dot(W2, A1) + b2
y_pred = sigmoid(Z2)

print ('Accuracy: %d' % float((np.dot(y, y_pred.T) + np.dot(1-y, 1-y_pred.T))/float(y.size)*100) + '%')


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

def det(i):
    ans = i[0,0]*i[1,1]*i[2,2] + i[0,1]*i[1,2]*i[2,0]+i[0,2]*i[1,0]*i[2,1] - i[0,2]*i[1,1]*i[2,0] - i[0,0]*i[1,2]*i[2,1] - i[0,1]*i[1,0]*i[2,2]
    return ans

def def_re(i):
    print('mathans:' + str(det(i)))
    print('pyans  :' + str(LA.det(i)))

a = np.array([[1,2,3],
              [4,5,6],
              [7,8,9]])

b = np.array([[1,1,3],
              [4,78,6],
              [7,8,-4]])

print(a,type(a))

def_re(b)
def_re(a@b)
def_re(a)

print(LA.det(a))
print("#####")
print(det(a))

b = np.array([[1,2,3]])

print(b,type(b))

print(a@b.T)
# print(a@b) Undifined


import numpy as np

class KMeans:

    def __init__(self, k, dataset):
        self.dataset = dataset
        self.k = k
        self.centroids = []

        # Randomly choose centroids
        self.randomize_centroids()

    @staticmethod
    def distance(point, centroid):
        return np.linalg.norm(point-centroid)

    @property
    def predicted(self):
        return np.array([self.predict(p) for p in self.dataset])

    def randomize_centroids(self):
        self.centroids = self.dataset[np.random.choice(len(self.dataset), self.k, replace=False), :]

    def get_prepared(self):
        return zip(self.dataset, self.predicted)

    def get_points_in_clusters(self):
        return [self.dataset[np.where(self.predicted == c)] for c in range(self.k)]

    def error(self):
        distances = [self.distance(point, self.centroids[int(cluster)]) for point, cluster in self.get_prepared()]
        return 1 / len(self.dataset) * sum(distances)

    def predict(self, point):
        return int(np.argmin([self.distance(point, centroid) for centroid in self.centroids]))

    def train(self, epochs=50):
        minimal_error = np.inf
        minimal_centroids = self.centroids

        for e in range(epochs):
            self.randomize_centroids()
            self.update_centroids()

            if self.error() < minimal_error:
                minimal_centroids = self.centroids
                minimal_error = self.error()

        self.centroids = minimal_centroids

    def update_centroids(self):
        predicted = None
        while (predicted is None) or (not all(predicted == self.predicted)):
            predicted = self.predicted
            self.centroids = list(map(lambda x: np.sum(x, axis=0) / (len(x) or 1), self.get_points_in_clusters()))


# Import packages
import numpy as np
from numpy import inf
from keras import backend as K
import math

# Tangens hyperbolicus function
def tanh(x, derivative=False):
    if (derivative):

        return (1 - (x**2))

    return ((np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x)))

# Tangens hyperbolicus function with inf check
def tanhyperbolic(x, derivative=False):
    output = ((np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x)))

    # Check if output contains any NaN values
    if np.isnan(output).any():

        if -math.inf in x:
            output = np.nan_to_num(output, nan=-1)

        if -math.inf in x:

            output = np.nan_to_num(output, nan=1)

    if (derivative):

        return (1 - (x**2))

    return output

# Inverted symmetric sigmoid function with inf check
def inv_sigmoid_inversion(x, derivative=False):

    if np.isnan(x).any() or np.isinf(x).any():
        x = np.nan_to_num(x, neginf=-0.5, posinf=0.5)

    if (derivative):

        if np.isnan(x).any() or np.isinf(x).any():
            x = np.nan_to_num(x, neginf=-0.5, posinf=0.5)

        return -1 / ((x**2) - 0.25)

    return (-K.log((1 / (x + 0.5) - 1)))

# Inverted symmetric sigmoid function
def inv_sigmoid(x, derivative=False):

    if (derivative):
        return -1 / ((x**2) - 0.25)

    return (-K.log((1 / (x + 0.5) - 1)))

# Symmetric sigmoid function
def sigmoid(x, derivative=False):

    if (derivative):
        return np.exp(-x) / ((np.exp(-x) + 1)**2)

    return ((1 / (1 + np.exp(-x))) - 0.5)

# Linear Function
def linear(x, derivative=False):
    return x


from em import EM

import numpy as np
from scipy.stats import multivariate_normal
import math
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import time
import os

if __name__ == "__main__":

  if not os.path.exists('plots'):
    os.makedirs('plots')

  plt.rcParams['figure.figsize'] = [15, 15]
  X,y = make_blobs(n_samples=200, centers=[[2.5,3.5],[3.5,5.5],[4,10]])

  em = EM(X,num_clusters=3)
  means,sigmas = em.cluster_means,em.cluster_cov_mtrxs
  iters = 16
  color_vals = ['red','blue','green']
  for i in np.arange(iters):
      print(f'Performing iteration {i}/{iters-1}..')

      plt.clf()
      plt.axis([-5, 30, -5, 30])
      plt.scatter(X[:,0],X[:,1],marker='o', c=y,
                      s=35, edgecolor='k')
      j = 0
      for mu,cov in zip(means,sigmas):
          x_plt, y_plt = np.mgrid[mu[0] - 3*cov[0,0]:mu[0] +3*cov[0,0]:.01, mu[1] - 3*cov[1,1]:mu[1] + 3*cov[1,1]:.01]
          pos = np.empty(x_plt.shape + (2,))
          pos[:, :, 0] = x_plt
          pos[:, :, 1] = y_plt
          rv = multivariate_normal(mu, cov)
          plt.contour(x_plt, y_plt, rv.pdf(pos),colors = color_vals[j])
          j+=1
      plt.savefig(f'plots/frame{i}.png')
      means,sigmas = em.iterate()


import numpy as np
from sklearn import preprocessing
import matplotlib.pyplot as plt

# Load the dataset of orb features from file.
orb_features = np.genfromtxt(open("orb_features.txt"))
orb_features = orb_features.T

orb_size = len(orb_features[:, 0])
# TODO: Compute covariance (exchange the value for None).
cov = None

# TODO: Compute eigenvectors and eigenvalues (exchange the value for None).
eig_val_cov = None
eig_vec_cov = None

# Sort eigenvectors and corresponding eigenvalues in descending order.
eig_pairs = [(np.abs(eig_val_cov[i]), eig_vec_cov[:, i]) for i in range(len(eig_val_cov))]
eig_pairs.sort(key=lambda x: x[0], reverse=True)

# TODO: Compute 5 dimensional feature vector based on largest eigenvalues and normalize the output (exchange the value for None).
pca_features = None

# Normalize pca features.
pca_features = preprocessing.scale(pca_features)

# 2D plot of first 2 principal components.
plt.scatter(pca_features[:, 0], pca_features[:, 1], marker = 'o')
plt.xlabel('principal component 1')
plt.ylabel('principal component 2')
plt.title('PCA result')
plt.show()

# Save results.
np.savetxt('results_pca.txt', pca_features)


# Monte Carlo Valuation of a European Option in a Black-Scholes World
# With implementation of Antithetic and Delta-based control variate method
# by Michal Lyskawinski
# 10/31/2016

from math import *
import numpy as np
import random
from scipy.stats import norm

# Computation of Black-Scholes
def CBS(S, K, T, r, sigma,t, option):

    t2t = T-t # time to maturity
    # Calculations for the solution to BSM equation
    dplus = (1 / (sigma * sqrt(t2t))) * ((log(S / K)) + (r + ((sigma ** 2) / 2)) * t2t)
    dminus = (1 / (sigma * sqrt(t2t))) * ((log(S / K)) + (r - (sigma ** 2) / 2) * t2t)

    # Calculating price of Call and Put
    if option == 'Call':
        return S * norm.cdf(dplus) - K * exp(-r * t2t) * norm.cdf(dminus)
    elif option == 'Put':
        return K * exp(-r * t2t) * norm.cdf(-dminus) - S * norm.cdf(-dplus)

# Initialize parameters
S = 100
r = 0.06
sig = 0.2
T = 1
K = 100
N = 10
M = 100
div = 0.03  # In percentage
option = 'Put'

# Precompute constants
dt = T/N
nu = r - div - 0.5*(sig**2)
nudt = nu*dt
sigsdt = sig*sqrt(dt)
erddt = exp((r-div)*dt)
beta1 = -1

sum_CT = 0
sum_CT2 = 0

for j in range(1,M): # For each simulation
    St1 = S
    St2 = S
    cv1 = 0
    cv2 = 0

    for i in range(1,N): # For each time step
        t = (i - 1) * dt
        delta1 = CBS(St1, K, T, r, sig, t, option)
        delta2 = CBS(St2, K, T, r, sig, t, option)
        eps = np.random.normal(0, 1)
        Stn1 = St1 * exp(nudt + sigsdt * eps)
        Stn2 = St2 * exp(nudt + sigsdt * (-eps))
        cv1a = cv1 + delta1 * (Stn1 - St1 * erddt)
        cv2a = cv2 + delta2 * (Stn2 - St2 * erddt)
        St1 = Stn1
        St2 = Stn2

    if option == 'Call':
        CT = 0.5*(max(0, St1 - K) + beta1*cv1a + max(0, St2 - K) + beta1*cv2a)
        sum_CT = sum_CT + CT
        sum_CT2 = sum_CT2 + CT*CT
    elif option == 'Put':
        CT = 0.5*(max(0, K - St1) + beta1*cv1a + max(0, K - St2) + beta1*cv2a)
        sum_CT = sum_CT + CT
        sum_CT2 = sum_CT2 + CT * CT
    else:
        break

Value = sum_CT/M*exp(-r*T)
SD = sqrt((sum_CT2 - sum_CT*sum_CT/M)*exp(-2*r*T)/(M-1))
SE = SD/sqrt(M)

print('The Value of European',option,'Option is',Value)
print('The Standard Deviation of this Option is',SD)
print('The Standard Error in this case is',SE)


import math
import statistics as stats
import numpy as np
from Modulos.Utils import Truncate

def FuncionAcumuladaExponencial(x, valor_lambda):
    '''La función devuelve el valor de la función acumulada para la distribución exponencial valuada en X
       Parámetros: x: variable a valuar la función
                   valor_lambda: valor del lambda calculado para la serie'''
    return 1-math.exp(-valor_lambda*x)

def FuncionDensidadNormal(x, media, desviacion_estandar):
    '''La función devuelve el valor de la función de densidad para la distribución normal valuada en X
       Parámetros: x: variable a valuar la función
                   media: media calculada para la serie
                   desviacion_estandar: desviación estandar calculada para la serie'''
    return (math.exp(-0.5*((x-media)/desviacion_estandar)**2))/(desviacion_estandar*math.sqrt(2*math.pi))

def FuncionAcumuladaUniforme(a, b, x):
    '''La función devuelve el valor de la función acumulada para la distribución uniforme valuada en X
        Parámetros: x: variable a valuar la función
                    a: extremo superior
                    b: extremo inferior'''
    return (x-a)/(b-a)

def ProbabilidadAcumuladaExponencial(desde, hasta, valor_lambda):
    '''La función devuelve el valor de la probabilidad acumulada para la distribución Exponencial
       Parámetros: desde: valor inicial del intervalo
                   hasta: valor final del intervalo
                   valor_lambda: valor del lambda calculado para la serie'''
    return FuncionAcumuladaExponencial(hasta, valor_lambda) - FuncionAcumuladaExponencial(desde, valor_lambda)

def ProbabilidadAcumuladaUniforme(desde, hasta, a, b):
    '''La función devuelve el valor de la probabilidad acumulada para la distribución uniforme
        Parámetros: desde: valor inicial del intervalo
                    hasta: valor final del intervalo
                    a: extremo superior
                    b: extremo inferior'''
    return FuncionAcumuladaUniforme(a, b, hasta) - FuncionAcumuladaUniforme(a, b, desde)

def FrecuenciasEsperadas(tamaño_muestra, intervalos, tipoDistribucion, media, desviacion_estandar, a, b):
    '''La función calcula la frecuencia esperada para cada intervalo según el tipo de distribución elegida
       Parámetros: tamaño_muestra: entero que representa la cantidad de elementos de la serie
                   intervalos: intervalos Dict<str, extremos> Diccionario que utiliza como clave la representacion del intervalo
                   tipoDistribucion: entero que representa el tipo de distribución elegida como hipótesis nula (0=uniforme, 1=exponencial, 2=normal)
                   media: media calculada para la serie
                   desviacion_estandar: desviacion estandar calculada para la serie
                   a: valor minimo de la serie
                   b: valor maximo de la serie
       Return: lista con frecuencias esperadas'''

    frec_esp_arr = []
    if tipoDistribucion == 1:
        valor_lambda = Truncate(1/media, 7)

    for i in intervalos:
        intervalo = intervalos[i]
        desde, hasta = intervalo[0], intervalo[1]
        if tipoDistribucion == 0:
          prob = ProbabilidadAcumuladaUniforme(desde, hasta, a, b)
          frec_esp = round(prob*tamaño_muestra)
        elif tipoDistribucion == 1:
          prob = ProbabilidadAcumuladaExponencial(desde, hasta, valor_lambda)
          frec_esp = Truncate(prob*tamaño_muestra, 4)

        elif tipoDistribucion == 2:
          marca_clase = (desde+hasta)/2
          prob = FuncionDensidadNormal(marca_clase, media, desviacion_estandar) * (hasta-desde)
          frec_esp = Truncate(prob*tamaño_muestra, 4)

        frec_esp_arr.append(frec_esp)

    return frec_esp_arr

def testFrecuenciasEsperadasExponencial():
    arr = [0.10, 0.25, 1.53, 2.83, 3.50, 4.14, 5.65, 6.96, 7.19, 8.25,1.20, 5.24, 4.75, 3.96, 2.21, 3.15, 2.53, 1.16, 0.32, 0.90, 0.87, 1.34, 1.87, 2.91, 0.71, 1.69, 0.69, 0.55, 0.43, 0.26]
    intervalos = {'0 - 1': [0, 1], '1 - 2': [1, 2], '2 - 3': [2, 3], '3 - 4': [3, 4], '4 - 5': [4, 5], '5 - 6': [5, 6], '6 - 7': [6, 7], '7 - 8': [7, 8], '8 - 9': [8, 9], '9 - 10': [9, 10]}
    tipoDistribucion = 1
    tamaño_muestra = 30
    a, b = min(arr), max(arr)
    media = stats.mean(arr)
    print('Media:', media)
    desviacion_estandar = np.std(arr, ddof=1)
    frec_esp = FrecuenciasEsperadas(tamaño_muestra, intervalos, tipoDistribucion, media, desviacion_estandar, a, b)
    print(frec_esp)

def testFrecuenciasEsperadasNormal():
    arr = [1.56, 2.21, 3.15, 4.61, 4.18, 5.20, 6.94, 7.71, 5.15, 6.76, 7.28, 4.23, 3.21, 2.75, 4.69, 5.86, 6.25, 4.27, 4.91, 4.78, 2.46, 3.97, 5.71, 6.19, 4.20, 3.48, 5.83, 6.36, 5.90, 5.43]
    intervalos = {'0 - 1': [0, 1], '1 - 2': [1, 2], '2 - 3': [2, 3], '3 - 4': [3, 4], '4 - 5': [4, 5], '5 - 6': [5, 6], '6 - 7': [6, 7], '7 - 8': [7, 8], '8 - 9': [8, 9], '9 - 10': [9, 10]}
    media = stats.mean(arr)
    print('Media:', media)
    desviacion_estandar = np.std(arr, ddof=1)
    print('Desv estandar: ', desviacion_estandar)
    tipoDistribucion = 2
    tamaño_muestra = 30
    a, b = min(arr), max(arr)
    frec_esp = FrecuenciasEsperadas(tamaño_muestra, intervalos, tipoDistribucion, media, desviacion_estandar, a, b)
    print(frec_esp)

def testFrecuenciasEsperadasUniforme():
    arr = [0.15, 0.22, 0.41, 0.65, 0.84, 0.81, 0.62, 0.45, 0.32, 0.07, 0.11, 0.29, 0.58, 0.73, 0.93, 0.97, 0.79, 0.55, 0.35, 0.09, 0.99, 0.51, 0.35, 0.02, 0.19, 0.24, 0.98, 0.10, 0.31, 0.17]
    intervalos = {'0.0 - 0.2': [0.0, 0.2], '0.2 - 0.4': [0.2, 0.4], '0.4 - 0.6': [0.4, 0.6], '0.6 - 0.8': [0.6, 0.8], '0.8 - 1.0': [0.8, 1.0]}
    media = stats.mean(arr)
    desviacion_estandar = np.std(arr, ddof=1)
    tipoDistribucion = 0
    tamaño_muestra = 30
    a, b = min(arr), max(arr)
    print(a,b)
    frec_esp = FrecuenciasEsperadas(tamaño_muestra, intervalos, tipoDistribucion, media, desviacion_estandar, a, b)
    print(frec_esp)


import numpy as np

def kyle_lambda(prices: np.ndarray, volumes: np.ndarray) -> np.ndarray:
    r"""Kyle's Lambda

    A measure of market impact cost from [Kyle (1985)](https://doi.org/10.2307/1913210),
    which can be interpreted as the cost of demanding a certain amount of liquidity over a given time period.

    It is also used as a measure of market liquidity and can be estimated by the
    volume required to move the price of a security by one dollar. Sometimes referred
    to as Kyles lambda, this measure is an inverse proxy of liquidity, with higher
    values of lambda implying lower liquidity and market depth. The authors estimate
    this measure on a daily basis by using all transactions during normal trading hours
    on each day.

    Given the sequence of

    * intraday returns $[R_{i,1}, R_{i,2}, ..., R_{i,T}]$,
    * prices $[p_{i,1}, p_{i,2}, ..., p_{i,T}]$, and
    * volumes $[v_{i,1}, v_{i,2}, ..., v_{i,T}]$

    for security $i$ during a specific day, the following regression is estimated:

    $$
    R_{i,t} = \alpha_i + \lambda_i \text{Sgn}(t) \ln(v_{i,t}\times p_{i,t}) + \varepsilon_{i,t}
    $$

    where $\text{Sgn}(t)$ is -1 or + 1 depending on the direction of the trade, i.e., “buy” or “sell”,
    as determined according to the following rule:

    * if $R_{i,t}$ is positive, the value +1 is assigned to that transaction (to indicate net buying),
    * if $R_{i,t}$ is negative, the value −1 is assigned to that transaction (to indicate net selling).

    Any interval with zero return receives the same sign as that of the most recent transaction with a non-zero return
    (using returns from the prior day, if necessary).

    Args:
        prices (np.ndarray): (n_securities, n_periods) array of security prices
        volumes (np.ndarray): (n_securities, n_periods) array of security volumes

    Returns:
        np.ndarray: Array of Kyle's Lambda estimates for the securities

        !!! note
            The `prices` data is used to calculate the asset returns, which is 1 less than the number of
            prices. As such, the `volumes` data of the first period is not used in the calculation.

    Examples:
        >>> import numpy as np
        >>> from frds.measures import kyle_lambda

        3 assets daily volume for 6 days
        >>> volumes = np.array(
        ...     [[100, 180, 900, 970, 430, 110], [200, 250, 400, 590, 260, 600], [300, 700, 220, 110, 290, 310]]
        ... )

        Their daily prices
        >>> prices = np.array(
        ...     [[44, 39, 36, 28, 23, 18], [82, 81, 79, 40, 26, 13], [55, 67, 13, 72, 10, 65]]
        ... )

        Calculate their respective Kyle's Lambda estimates.
        >>> kyle_lambda(prices, volumes)
        array([-0.02198189, -0.1951004 ,  0.22752204])

    References:
        * [Kyle (1985)](https://doi.org/10.2307/1913210),
            Continuous auctions and insider trading, *Econometrica*, 53, 1315-1355.
        * [Bisias, Flood, Lo, and Valavanis (2012)](https://doi.org/10.1146/annurev-financial-110311-101754),
            A survey of systemic risk analytics, *Annual Review of Financial Economics*, 4, 255-296.
    """

    assert volumes.shape == prices.shape

    n_securities, n_periods = prices.shape
    # number of returns is 1 less than prices
    n_periods -= 1

    # calculate the asset returns
    returns = np.diff(prices, axis=1) / np.roll(prices, 1)[:, 1:]

    # volumes and prices of the first period are not used
    volumes = np.delete(volumes, (0), axis=1)  # volumes = volumes[:,1:]
    prices = np.delete(prices, (0), axis=1)  # prices = prices[:, 1:]

    lambdas = np.zeros(n_securities)
    for i in range(n_securities):
        mod_signs = np.zeros(n_periods)
        np.sign(returns[i], out=mod_signs)
        for t in range(1, len(mod_signs)):
            if mod_signs[t] == 0:
                mod_signs[t] = mod_signs[t - 1]

        X = np.vstack([np.ones(n_periods), mod_signs * np.log(prices[i] * volumes[i])])
        betas = np.linalg.lstsq(X.T, returns[i], rcond=None)[0]
        lambdas[i] = betas[1]

    return lambdas


import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as st

def show_null_values(df: pd.DataFrame):
    """
    Show the number of null values.
    """
    plt.figure(figsize=(12, 8))
    missing = df.isnull().sum()
    missing = missing[missing > 0]
    missing.sort_values(ascending=False, inplace=True)
    print(missing)
    missing.plot.bar()
    plt.show()

def show_distributions(df: pd.DataFrame, column: str):
    """
    Show the distribution of numerical features.
    """
    if df[column].dtype not in ['float', 'int']:
        raise Exception("Can only display distribution of numerical features")
    f = df[column]
    plt.figure(1)
    plt.title('Johnson SU')
    sns.distplot(f, kde=False, fit=st.johnsonsu)
    plt.figure(2)
    plt.title('Normal')
    sns.distplot(f, kde=False, fit=st.norm)
    plt.figure(3)
    plt.title('Log Normal')
    sns.distplot(f, kde=False, fit=st.lognorm)
    plt.show()

def show_all_num_distributions(df: pd.DataFrame,
                               num_columns,
                               plot_func=sns.distplot):
    """
    Show distributions of all numerical features.

    Parameters
    ----------
    df: data; type: DataFrame.
    num_columns: numerical culumn names; type: Tuple | List | ndarray | None.
    plot_func: function to draw; ex: sns.distplot、sns.barplot or customized function.
    """
    f = pd.melt(df, value_vars=num_columns)
    g = sns.FacetGrid(f,
                      col="variable",
                      col_wrap=2,
                      sharex=False,
                      sharey=False)
    g = g.map(plot_func, "value")
    plt.show()

def show_pairs_relationship(df: pd.DataFrame, num_columns: list):
    """
    Show relationship between numerical column pairs.
    Parameters
    ----------
    df: data; type: DataFrame.
    num_columns: numerical culumn names; type: List.
    """
    sns.set()
    sns.pairplot(df[num_columns], size=2, kind="scatter", diag_kind="kde")
    plt.show()

def show_correlation(df: pd.DataFrame, num_columns):
    """
    Show correlation of numerical columns.
    Parameters
    ----------
    df: data; type: DataFrame.
    num_columns: numerical culumn names; type: List.
    """
    correlation = df[num_columns].corr()
    plt.figure(figsize=(8, 8))
    plt.title('Correlation of Numeric Features with Price', size=16)
    sns.heatmap(correlation, square=True)
    plt.show()

if __name__ == '__main__':
    data_path = "D:/download/data/house/train.csv"
    data = pd.read_csv(data_path)
    num_columns = data.select_dtypes(include=['float', 'int']).columns
    print(num_columns)
    # show_distributions(data, 'SalePrice')
    show_correlation(data, num_columns)


#!/usr/bin/python
# -*- coding: latin-1 -*-
"""
Utilities contains a number of frequently used functions.
"""

import numpy as np
import numpy.linalg as LA

def normalize(array):
    """
    Returns a normalized version of the provided array.

    Parameters
    ----------
    array : numpy.ndarray_like
        The array to be normalized. Afterwards np.linalg.norm(normalize(array))
        is approximately equal to 1.

    Returns
    -------
    normalized array : numpy.ndarray_like
        the array normalized to unit length:
        np.linalg.norm(normalize(array)) ~= 1.

    Raises
    ------
    ValueError
        if the input array contains Inf's or Nan's
    ZeroDivisionError
        if the length of the provided array has length ofzero the division will
        cause a ZeroDivisionError to be raised

    Examples
    --------
    >>> normalize([1, 0, 0])
    [1.0, 0.0, 0.0]
    >>> normalize([2, 4, 4])
    [0.33333333, 0.66666667, 0.66666667]
    >>> normalize(np.array((3,4,5)))
    [0.42426407, 0.56568542, 0.70710678]
    ### np.linalg.norm(normalize((x, y, z))) ~= 1
    """
    if not np.isfinite(array).all():
        raise ValueError("Can not normalize array {}".format(array)
                        + "because it contains NaN or Inf")
    norm = LA.norm(array)
    if norm < 1e-9:
        raise ZeroDivisionError("Division by zero")
    return array / norm

def check_finite(*args):
    """
    Validate the input parameters and raise ValueErrors if any contains
    incompatible values (Infs or NaNs) are present.

    Parameters
    ----------
    args : numpy.ndarray_like
        a list of lists or arrays

    Raises
    ------
    ValueError
        if any passed in element is Inf or NaN.
    """
    for e in args:
        if not np.isfinite(e).all():
            raise ValueError("Input was {}".format(e)
                            + ", but can not contain Inf or NaN")


import numpy as np

def uniform_mesh(d1, d2, p, m , element_type : str):
    PD  = 2  # Problem Dimension
    q   = np.array([[0, 0], [d1, 0], [0, d2], [d1, d2]]) # 4 corners
    NoN = (p + 1) * (m + 1)
    NoE = p*m
    NPE = 4

    ### Nodes ###
    NL  = np.zeros([NoN, PD])
    a   = (q[1, 0] - q[0, 0])/p # Increment in the horizontal dir.
    b   = (q[2, 1] - q[0, 1])/m # Increment in the Vertical dir.

    n   = 0 # This will allow us to go through rows in NL
    for i in range(1, m+2):         # Vertical loop
        for j in range(1, p+2):     # Horizontal loop
            NL[n, 0]    = q[0, 0] + (j - 1)*a   # x values
            NL[n, 1]    = q[0, 1] + (i - 1)*b   # y values
            n += 1

    ### Elements ###
    EL = np.zeros([NoE, NPE])
    for i in range(1, m+1):         # Vertical loop
        for j in range(1, p+1):     # Horizontal loop
            if j == 1:
                EL[(i-1)*p + j - 1, 0] = (i - 1)*(p + 1) + j
                EL[(i-1)*p + j - 1, 1] = EL[(i-1)*p + j - 1, 0] + 1
                EL[(i-1)*p + j - 1, 3] = EL[(i-1)*p + j - 1, 0] + (p + 1)
                EL[(i-1)*p + j - 1, 2] = EL[(i-1)*p + j - 1, 3] + 1
            else:
                EL[(i-1)*p + j - 1, 0] = EL[(i-1)*p + j - 2, 1]
                EL[(i-1)*p + j - 1, 3] = EL[(i-1)*p + j - 2, 2]
                EL[(i-1)*p + j - 1, 1] = EL[(i-1)*p + j - 1, 0] + 1
                EL[(i-1)*p + j - 1, 2] = EL[(i-1)*p + j - 1, 3] + 1

    if element_type == 'D2TR3N':
        NPE_new = 3
        NoE_new = 2*NoE
        EL_new  = np.zeros([NoE_new, NPE_new])

        for i in range(1, NoE+1):
            #for the first triangular element
            EL_new[2*(i-1), 0] = EL[i-1, 0]
            EL_new[2*(i-1), 1] = EL[i-1, 1]
            EL_new[2*(i-1), 2] = EL[i-1, 2]

            #for the second triangular element
            EL_new[2*(i-1) + 1, 0] = EL[i-1, 0]
            EL_new[2*(i-1) + 1, 1] = EL[i-1, 2]
            EL_new[2*(i-1) + 1, 2] = EL[i-1, 3]

        EL = EL_new

    EL = EL.astype(int)
    return (NL, EL)


import numpy as np

def gc1d(x):
    """
    Compute Gini Index for 1D array.

    References
    ----------
    [1] http://mathworld.wolfram.com/GiniCoefficient.html

    [2] Damgaard, C. and Weiner, J. "Describing Inequality in Plant Size
    or Fecundity." Ecology 81, 1139-1142, 2000.

    [3] Dixon, P. M.; Weiner, J.; Mitchell-Olds, T.; and Woodley, R.
    "Bootstrapping the Gini Coefficient of Inequality."
    Ecology 68, 1548-1551, 1987.

    [4] Dixon, P. M.; Weiner, J.; Mitchell-Olds, T.; and Woodley, R.
    "Erratum to 'Bootstrapping the Gini Coefficient of Inequality.' "
    Ecology 69, 1307, 1988.

    [5] https://en.wikipedia.org/wiki/Gini_coefficient

    [6] https://github.com/oliviaguest/gini/blob/master/gini.py
    """
    x = np.array(x, dtype="float64")
    if x.ndim != 1:
        raise ValueError("Only support 1D array.")
    if x.shape[0] == 0:
        raise ValueError("Array size cannot be 0.")
    if x.shape[0] == 1:
        return np.nan
    # sort into ascending order
    xs = np.sort(x)
    if xs[0] == xs[-1]:
        return 0
    # Wrap with np.int64 to prevent division by 0
    n = np.int64(x.shape[0])
    xs_ranks = np.arange(1, n+1)
    xmean = x.mean()
    gc = ((2 * xs_ranks - n - 1) * xs).sum() / (n * (n-1) * xmean)
    return gc

# Multiple testing correction adapted from
# https://github.com/CoBiG2/cobig_misc_scripts/blob/master/FDR.py
#
# Following is the lincense info:
# Copyright 2017 Francisco Pina Martins <f.pinamartins@gmail.com>
# This file is part of structure_threader.
# structure_threader is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# structure_threader is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with structure_threader. If not, see <http://www.gnu.org/licenses/>.
#
# Taken from https://stackoverflow.com/a/21739593/3091595, ported to python 3
# and improved readability.
def multiple_testing_correction(pvalues, correction_type="FDR"):
    """
    Consistent with R.

    correct_pvalues_for_multiple_testing([0.0, 0.01, 0.029, 0.03, 0.031, 0.05,
                                          0.069, 0.07, 0.071, 0.09, 0.1])
    """
    pvalues = np.array(pvalues)
    sample_size = pvalues.shape[0]
    qvalues = np.empty(sample_size)
    if correction_type == "Bonferroni":
        # Bonferroni correction
        qvalues = sample_size * pvalues
    elif correction_type == "FDR":
        # Benjamini-Hochberg, AKA - FDR test
        values = [(pvalue, i) for i, pvalue in enumerate(pvalues)]
        values.sort()
        values.reverse()
        new_values = []
        for i, vals in enumerate(values):
            rank = sample_size - i
            pvalue, index = vals
            new_values.append((sample_size/rank) * pvalue)
        for i in range(0, int(sample_size)-1):
            if new_values[i] < new_values[i+1]:
                new_values[i+1] = new_values[i]
        for i, vals in enumerate(values):
            pvalue, index = vals
            qvalues[index] = new_values[i]
    else:
        raise ValueError("Not supported correction type "
                         "{}".format(correction_type))
    return np.clip(qvalues, 0, 1)

# lower upper bound
# start, end, lb, ub should all be scalar
def bidir_ReLU(x, start, end, lb=0, ub=1):
    if start > end:
        raise ValueError("start should <= end"
                         "start: {}. end: {}.".format(start, end))

    if lb > ub:
        raise ValueError("lb should <= ub"
                         "lower bound: {}. "
                         "upper bound: {}. ".format(start, end))

    if start < end:
        width = end - start
        height = ub - lb
        return np.clip(a=height * (x - start) / width + lb,
                       a_min=lb, a_max=ub)
    else:
        # start == end
        return np.where(x >= start, ub, lb)


# 施密特正交化 Gram-Schmidt
# ！注意 例子中 输入向量为  (3, 1)T  (2, 2)T
# 输出向量为 (0.9., 0.3.)T (-0.3., 0.9.)T

import numpy as np

def myGS(V):
    u = V.copy().transpose()
    E = []
    for i in range(len(u)):
        for j in range(i):
            u[i] = (V[i] @ u[j]) / (u[j] @ u[j]) * (u[j])
        E.append(u[i] / np.linalg.norm(u[i]))
    return np.array(E)

if __name__ == "__main__":
    # 输入矩阵
    # V = np.array([[3., 1.], [2., 2.]])
    V = np.array([[1., 0, -1, 4], [-1, 2, 2, -1], [2, 3, 1, -3]]).transpose()
    print(V)

    # # 输出施密特正交化结果
    E = myGS(V)
    print(E)

    # # 验证是否为单位阵
    print(E.transpose() @ E)


# -*- coding: utf-8 -*-
"""1.K-Means_Algorithm

#Basic K-Means Implementations ::::

#Implementation 1 -->>

Generating different blobs
"""

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from sklearn.datasets.samples_generator import make_blobs
from sklearn.cluster import KMeans

X, y = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=0)
plt.scatter(X[:,0], X[:,1])

"""WCSS is defined as the sum of the squared distance between each member of the cluster and its centroid."""

wcss = []
for i in range(1, 11):
    kmeans = KMeans(n_clusters=i, init='k-means++', max_iter=300, n_init=10, random_state=0)
    kmeans.fit(X)
    wcss.append(kmeans.inertia_)
plt.plot(range(1, 11), wcss)
plt.title('Elbow Method')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS')
plt.show()

kmeans = KMeans(n_clusters=4, init='k-means++', max_iter=300, n_init=10, random_state=0)
pred_y = kmeans.fit_predict(X)
plt.scatter(X[:,0], X[:,1])
plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], s=300, c='red')
plt.show()

"""#Implementation 2 -->>

Generating 2D dataset containing 4 different blobs
"""

# Commented out IPython magic to ensure Python compatibility.
# %matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
import numpy as np
from sklearn.cluster import KMeans

#The following code will generate the 2D, containing four blobs −
from sklearn.datasets.samples_generator import make_blobs
X, y_true = make_blobs(n_samples = 400, centers = 4, cluster_std = 0.60, random_state = 0)

#visualize the dataset −
plt.scatter(X[:, 0], X[:, 1], s = 20);
plt.show()

#do the prediction as follows −
kmeans = KMeans(n_clusters = 4)
kmeans.fit(X)
y_kmeans = kmeans.predict(X)

from sklearn.datasets.samples_generator import make_blobs
X, y_true = make_blobs(n_samples = 400, centers = 4, cluster_std = 0.60, random_state = 0)

#visualize the prediction −
plt.scatter(X[:, 0], X[:, 1], c = y_kmeans, s = 20, cmap = 'summer')
centers = kmeans.cluster_centers_
plt.scatter(centers[:, 0], centers[:, 1], c = 'blue', s = 100, alpha = 0.9);
plt.show()

"""#Implementation 3->>

##simple digits dataset

Identifying similar digits without using the original label information.
"""

# Commented out IPython magic to ensure Python compatibility.
# %matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
import numpy as np
from sklearn.cluster import KMeans

from sklearn.datasets import load_digits
digits = load_digits()
digits.data.shape

kmeans = KMeans(n_clusters = 10, random_state = 0)
clusters = kmeans.fit_predict(digits.data)
kmeans.cluster_centers_.shape

fig, ax = plt.subplots(2, 5, figsize=(8, 3))
centers = kmeans.cluster_centers_.reshape(10, 8, 8)
for axi, center in zip(ax.flat, centers):
              axi.set(xticks=[], yticks=[])
              axi.imshow(center, interpolation='nearest', cmap=plt.cm.binary)

from scipy.stats import mode
labels = np.zeros_like(clusters)
for i in range(10):
   mask = (clusters == i)
   labels[mask] = mode(digits.target[mask])[0]
from sklearn.metrics import accuracy_score
accuracy_score(digits.target, labels)

"""###The above output shows that the accuracy is around 80%."""


"""
Sequential Probability Ratio Tests
"""
import math
import numpy as np
import scipy
from scipy.special import comb

############# User Function #################

def seq_prob_ratio(ho, ha, n, a, b, distribution, with_replacement=True):
	"""
	Performs sequential probability ratio test of desired distribution type.
	Parameters
	----------
	ho : float or int
       null hypothesis
    ha : float or int
       alternate hypothesis
    a : float
       Type I Error
    b : float
       Type II Error
    n : list
       list of population values
    with_replacement : boolean (default : True)
       True: sample from n with replacement
       False: sample from n without replacement
	distribution : string
		type of distribution : {"normal", "bernoulli", "hypergeometric"}

	Returns
	-------
	String
	   Conclusion of test
	Other value types (different per type of test)
	"""
	if distribution == "normal":
		return normal_seq_ratio(ho, ha, a, b, n, with_replacement)
	elif distribution == "bernoulli":
		return bernoulli_seq_ratio(ho, ha, n, a, b, with_replacement)
	elif distribution == "hypergeometric":
		return hypergeom_seq_ratio(ho, ha, a, b, n, with_replacement)
	else:
		raise ValueError

####### Private Helper Functions #######

def bernoulli_lh(ho, ha, s, n):
	"""
	Returns the likelihood ratio for independently distributed bernoulli random variables.
	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	s : float or int
	   number of successes in sample
	n : float or int
	   total number of elements in sample
	Returns
	-------
	float
	   likelihood ratio of model
	"""

	null_lh = (ho ** s) * (1 - ho)**(n - s)
	alt_lh = (ha ** s) * (1 - ha)**(n - s)

	return alt_lh / null_lh

def bernoulli_seq_ratio(ho, ha, n, a, b, with_replacement=True):
	"""
	Performs seqential probability ratio test on independently distributed bernoulli random variables.
	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	n : list of {0, 1}
	   list of population values
	a : float
	   Type I Error
	b : float
	   Type II Error
	Returns
	-------
	String
	   Conclusion of test
	List
	   [proportion of successes when test haults, sample size when test haults, string of conclusion]
	"""

	A, B = b / (1 - a), (1 - b) / a

	assert (A < B)

	def test(ho, ha, n, s, A, B, ss, ts):
		"""
		Samples from n with replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		n : list
		   list of population values
		s : int or float
		   current number of successes in sample
		A : float
		   lower bound parameter
		B : float
		   upper bound parameter
		ss : int
		   current sample size
		ts : float or int
		   current likelihood
		"""
		successes = s

		index = np.random.choice(len(n), 1, replace=True)[0]

		trial = n[index]

		ss += ss + 1

		if trial == 0:
			s += 1

		ts *= bernoulli_lh(ho, ha, s, ss)

		if ts >= B:
			return [s / ss, ss, "Accept null hypothesis."]
		elif ts <= A:
			return [s / ss, ss, "Reject null hypothesis."]
		else:
			return test(ho, ha, n, s, A, B, ss, ts)

	def test_wo(ho, ha, n, s, A, B, ss, ts):
		"""
		Samples from n without replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		n : list
		   list of population values
		s : int or float
		   current number of successes in sample
		A : float
		   lower bound parameter
		B : float
		   upper bound parameter
		ss : int
		   current sample size
		ts : float or int
		   current likelihood
		"""

		index = np.random.choice(len(n), 1, replace=True)[0]

		trial = n[index]

		n.pop(index)

		ss += ss + 1

		if trial == 0:
			s += 1

		ts *= bernoulli_lh(ho, ha, s, ss)

		if ts >= B:
			return [s / ss, ss, "Accept null hypothesis."]
		elif ts <= A:
			return [s / ss, ss, "Reject null hypothesis."]
		else:
			if ss == len(n):
				return [None, "No Decision."]
			return test(ho, ha, n, s, A, B, ss, ts)

	if with_replacement == True:
		return test(ho, ha, n, 0, A, B, 0, 1)

	return test_wo(ho, ha, n, 0, A, B, 0, 1)

def normal_lh(ho, ha, x):

	"""
	Returns likelihood ratio for indepedently distributed normal variables.
	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	x : float or int
	   value of most recent sample
	Returns
	-------
	float
	   likelihood of model
	"""
	null_lh = math.exp(-((x - ho)**2))
	alt_lh = math.exp(-((x - ha)**2))

	return alt_lh / null_lh

def normal_seq_ratio(ho, ha, a, b, n, with_replacement=False):

	"""
	Performs sequential probability ratio test on independently distributed normal random variables Xi.
	Parameters
	----------
	ho : float or int
	   null hypothesis
	ha : float or int
	   alternative hypothesis
	a : float
	   Type I Error
	b : float
	   Type II Error
	n : list
	   list of all possible values of Xi for i = {1, 2, .....n}
	with_replacement : boolean
	   False: sample from n without replacement
	   True: sample from n with replacement
	Returns
	-------
	string
	   conclusion of test
	list
	   [sample size when test haults, mean when test haults, string of conclusion]
	"""
	A, B = b / (1 - a), (1 - b) / a

	assert (A < B)

	def test(ho, ha, s, u, A, B, ss, ts, n):

		"""
		Samples from n with replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		s : int or float
		   cummulative sum of trial results
		u : float or int
		   current mean
		A : float
		   lower bound parameter
		B : float
		   upper bound parameter
		ss : int
		   current sample size
		ts : float or int
		   cumulative product of likelihood ratios
		n : list
		   list of all possible values of Xi where i = {1, 2....n}
		"""

		index = np.random.choice(len(n), 1, replace=True)[0]

		trial = n[index]

		s += trial

		ss += 1

		u = s / ss

		ts *= normal_lh(ho, ha, trial)

		if ts >= B:
			return [ss, u, "Accept null hypothesis."]
		elif ts <= A:
			return [ss, u, "Reject null hypothesis."]
		else:
			return test(ho, ha, s, u, A, B, ss, ts, n)

	def test_wo(ho, ha, s, u, A, B, ss, ts, n):

		"""
		Samples from n without replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		s : int or float
		   cummulative sum of trial results
		u : float or int
		   current mean
		A : float
		   lower bound parameter
		B : float
		   upper bound parameter
		ss : int
		   current sample size
		ts : float or int
		   cumulative product of likelihood ratios
		n : list
		   list of all possible values of Xi where i = {1, 2....n}
		"""

		index = np.random.choice(len(n), 1, replace=True)[0]

		trial = n[index]

		n.pop(index)

		s += trial

		ss += 1

		u = s / ss

		ts *= normal_lh(ho, ha, trial)

		if ts >= B:
			return [ss, u, "Accept null hypothesis."]
		elif ts <= A:
			return [ss, u, "Reject null hypothesis."]
		else:
			if ss == len(n):
				return [None, "No Decision"]
			return test_wo(ho, ha, s, u, A, B, ss, ts, n)

	if with_replacement == True:

		return test(ho, ha, 0, 0, A, B, 0, 1, n)

	return test_wo(ho, ha, 0, 0, A, B, 0, 1, n)

def hypergeom_lh(ho, ha, trial, n, g, N):
	"""
	Returns likelihood ratio for independently distributed hypergeometric random variables.

	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	trial : float
	   number of good elements in recent sample
	n : float or int
	   sample size
	g : float or int
	   number of good elements in sample
	N : float or int
	   total population size
	Returns
	-------
	float
	   likelihood ratio of model

	"""
	ho_G, ha_G = ho * (N / n), ha * (N / n)

	null_lh = (comb(ho_G, g) * comb(N - ho_G, n - g))
	alt_lh = (comb(ha_G, g) * comb(N - ha_G, n - g))

	return alt_lh / null_lh

def hypergeom_seq_ratio(ho, ha, a, b, N, with_replacement=True):

	"""
	Performs sequential probability ratio test on independently distributed hypergeometric random variables.

	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	a : float
	   Type I Error
	b : float
	   Type II Error
	N : list
	   list of elements in population
	with_replacement : boolean
	   True: sample from N with replacement
	   False: sample from N without replacement
	Returns
	-------
	float
	   mean when test haults
	string
	   conclusion of test
	"""

	A, B = b / (1 - a), (1 - b) / a

	assert (A < B)

	def test(ho, ha, A, B, g, n, N, ts):
		"""
		Samples from N with replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		A : float
		   lower parameter
		B : float
		   upper parameter
		g : float or int
		   number of good elements in sample
		n : float or int
		   current sample size
		N : list of {0, 1}
		   list of elements in total population
		ts : float
		   cumulative product of likelihood ratios
		"""

		index = np.random.choice(len(N), 1)[0]

		trial = N[index]

		n += 1

		if trial == 0:
			g += 1

		ts *= hypergeom_lh(ho, ha, trial, n, g, len(N))

		if ts >= B:
			return [n * (g/n), "Accept null hypothesis."]
		elif ts <= A:
			return [n * (g/n), "Reject null hypothesis."]
		else:
			return test(ho, ha, A, B, g, n, N, ts)

	def test_wo(ho, ha, A, B, g, n, N, ts):
		"""
		Samples from N without replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		g : float or int
		   number of good elements in sample
		n : float or int
		   current sample size
		N : list of {0, 1}
		   list of elements in total population
		ts : float
		   cumulative product of likelihood ratios
		"""

		index = np.random.choice(len(N), 1)[0]

		trial = N[index]

		N.pop(index)

		n += 1

		if trial == 0:
			g += 1

		ts *= hypergeom_lh(ho, ha, trial, n, g, len(N))

		if ts >= B:
			return [n * (g/n), "Accept null hypothesis."]
		elif ts <= A:
			return [n * (g/n), "Reject null hypoethesis."]
		else:
			if n == len(N):
				return [None, 'No Decision']
			return test_wo(ho, ha, A, B, g, n, N, ts)

	if with_replacement == True:
		return test(ho, ha, A, B, 0, 0, N, 1)

	return test_wo(ho, ha, A, B, 0, 0, N, 1)


import pylab as pl
import numpy as np

def tam(path, report='full'):
    """
    Calculates the Time Alignment Measurement (TAM) based on an optimal warping path
    between two time series.
    Reference: Folgado et. al, Time Alignment Measurement for Time Series, 2018.

    :param path: (ndarray)
                A nested array containing the optimal warping path between the
                two sequences.
    :param report: (string)
                A string containing the report mode parameter.
    :return:    In case ``report=instants`` the number of indexes in advance, delay and phase
                will be returned. For ``report=ratios``, the ratio of advance, delay and phase
                will be returned. In case ``report=distance``, only the TAM will be returned.

    """
    # Delay and advance counting
    delay = len(pl.find(np.diff(path[0]) == 0))
    advance = len(pl.find(np.diff(path[1]) == 0))

    # Phase counting
    incumbent = pl.find((np.diff(path[0]) == 1) * (np.diff(path[1]) == 1))
    phase = len(incumbent)

    # Estimated and reference time series duration.
    len_estimation = path[1][-1]
    len_ref = path[0][-1]

    p_advance = advance * 1. / len_ref
    p_delay = delay * 1. / len_estimation
    p_phase = phase * 1. / np.min([len_ref, len_estimation])

    if report == 'instants':
        return np.array([advance, delay, phase])

    if report == 'ratios':
        return np.array([advance, delay, phase])

    if report == 'distance':
        return p_advance + p_delay + (1 - p_phase)

    if report == 'full':
        return np.array([advance, delay, phase, p_advance + p_delay + (1 - p_phase)])


r"""
Solve Helmholtz equation in 2D with periodic bcs in one direction
and Dirichlet in the other

   alpha u - \nabla^2 u = f,

Use Fourier basis for the periodic direction and Shen's Dirichlet basis for the
non-periodic direction.

The equation to solve is

     alpha (u, v) - (\nabla^2 u, v) = (f, v)

"""
import sys
import os
import importlib
from sympy import symbols, cos, sin
import numpy as np
from mpi4py import MPI
from shenfun import inner, div, grad, TestFunction, TrialFunction, FunctionSpace, \
    Array, Function, TensorProductSpace, dx

comm = MPI.COMM_WORLD

assert len(sys.argv) == 3, "Call with two command-line arguments"
assert sys.argv[-1].lower() in ('legendre', 'chebyshev', 'jacobi')
assert isinstance(int(sys.argv[-2]), int)

# Collect basis and solver from either Chebyshev or Legendre submodules
family = sys.argv[-1]
base = importlib.import_module('.'.join(('shenfun', family)))
Solver = base.la.Helmholtz

# Use sympy to compute a rhs, given an analytical solution
alpha = 2.
x, y = symbols("x,y", real=True)
ue = (cos(4*np.pi*x) + sin(2*y))*(1-x**2)
fe = alpha*ue - ue.diff(x, 2) - ue.diff(y, 2)

# Size of discretization
N = (int(sys.argv[-2]),)*2

SD = FunctionSpace(N[0], family, bc=(0, 0), scaled=True)
K1 = FunctionSpace(N[1], 'F', dtype='d')
T = TensorProductSpace(comm, (SD, K1), axes=(0, 1))
u = TrialFunction(T)
v = TestFunction(T)

# Get f on quad points
fj = Array(T, buffer=fe)

# Compute right hand side of Poisson equation
f_hat = Function(T)
f_hat = inner(v, fj, output_array=f_hat)

# Get left hand side of Helmholtz equation
matrices = inner(v, alpha*u - div(grad(u)))

# Create Helmholtz linear algebra solver
H = Solver(*matrices)

# Solve and transform to real space
u_hat = Function(T)           # Solution spectral space
u_hat = H(u_hat, f_hat)       # Solve
uq = Array(T)
uq = T.backward(u_hat, uq)

# Compare with analytical solution
uj = Array(T, buffer=ue)
print("Error=%2.16e" %(np.sqrt(dx(uj-uq)**2)))
assert np.allclose(uj, uq)

if 'pytest' not in os.environ:
    import matplotlib.pyplot as plt
    plt.figure()
    X = T.local_mesh(True) # With broadcasting=True the shape of X is local_shape, even though the number of datapoints are still the same as in 1D
    plt.contourf(X[0], X[1], uq)
    plt.colorbar()

    plt.figure()
    plt.contourf(X[0], X[1], uj)
    plt.colorbar()

    plt.figure()
    plt.contourf(X[0], X[1], uq-uj)
    plt.colorbar()
    plt.title('Error')

    plt.show()


# type: ignore

"""
Author: David Oniani

Description:
    A collection of activation functions and their derivatives.
"""

import numpy as np

import miniml.tensor as T
import miniml.layer as L

class LeakyReLU:
    def __init__(self, out_dim: int = 3, alpha: float = 1e-2) -> None:
        """Initilize variables for the LeakyReLU activation function."""

        self._units: int = out_dim
        self._alpha: float = alpha

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        self._saved: T.Tensor = T.Ops.where(x > 0, x, x * self._alpha)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation.

        Gradient computation:
           (1) if x < 0, derivative is alpha
           (2) if x > 0, derivative is 1
           (3) if x = 0, derivative is not well-defined, but can treat as 1
        """

        return dF * T.Ops.where(self._saved < 0, self._alpha, 1)

class ReLU(L.Layer):
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the ReLU activation function."""

        self._units: int = out_dim
        self.type = "ReLU"

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        self._saved: T.Tensor = T.Ops.maximum(x, 0)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation.

        Gradient computation:
           (1) if x < 0, derivative is 0
           (2) if x > 0, derivative is 1
           (3) if x = 0, derivative is not well-defined, but can treat as 1
        """

        return dF * T.Ops.where(self._saved < 0, 0, 1)

class Tanh:
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the Tanh activation function."""

        self._units: int = out_dim

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        self._saved: T.Tensor = T.Ops.tanh(x)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation.

        Gradient computation:
            tanh(x) = (e^x - e^{-x}) / (e^x + e^{-x})

            tanh'(x) = ((e^x + e^{-x})^2 - (e^x - e^{-x})^2) / (e^x + e^{-x})^2
                     = 1 - ((e^x - e^{-x}) / (e^x + e^{-x}))^2
                     = 1 - tanh^2(x)
        """

        return dF * (1 - T.Ops.square(self._saved))

class Sigmoid(L.Layer):
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the Sigmoid activation function."""

        self._units: int = out_dim
        self.type = "ReLU"

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        fst: T.Tensor = 1 / (1 + T.Ops.exp(-x))
        pexp: T.Tensor = T.Ops.exp(x)
        snd: T.Tensor = pexp / (1 + pexp)
        self._saved: T.Tensor = T.Ops.where(x >= 0, fst, snd)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation.

        Gradient computation:
            If the sigmoid function is denoted as s, it can be shown that the
            derivative is computed as s * (1 - s).
        """

        return dF * self._saved * (1 - self._saved)

class Softmax:
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the Softmax activation function."""

        self._units: int = out_dim

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        exp: T.Tensor = T.Ops.exp(x - T.Reduce.max(x))
        self._saved: T.Tensor = exp / T.Reduce.sum(exp)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation."""

        raise NotImplementedError

class LogSoftmax:
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the LogSoftmax activation function."""

        self._units: int = out_dim

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        exp: T.Tensor = T.Ops.exp(x - T.Reduce.max(x))
        self._saved: T.Tensor = T.Ops.log(exp / T.Reduce.sum(exp))
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation."""

        raise NotImplementedError


## Adaptado de Nikhil Ketkar - Deep Learning with Python, A Hands-on Introduction
## Mostra o efeito da especializacao de um algoritmo ao incrementar o grau do
## polinomio utilizado no metodo de Minimos Quadrados
## Pega os primeiros 80 pontos pra treinar e os ultimos 20 pra testar

#Generate Toy Dataset
import pylab
import numpy
import matplotlib.pyplot as plt

degrees = [0, 1, 2, 3, 4, 5, 8, 10]
#degrees = []

numpy.random.seed (2)
x = numpy.linspace (-1,1,100)
signal = 2 + x + 2 * x * x
noise = numpy.random.normal (0, 0.1, 100)
y = signal + noise

plt.figure ()
plt.xticks (fontsize = 16)
plt.yticks (fontsize = 16)
plt.xticks (numpy.arange (0, 101, 10))
plt.yticks (numpy.arange (0, 5.1, 1))
plt.xlabel ('x', fontsize = 24)
plt.ylabel ('y', fontsize = 24)
#plt.ylabel ('$2x^2 + x + 2$', fontsize = 24)
plt.tight_layout ()

plt.plot (signal,'b');
plt.plot (y,'g', lw = 3, ls = ':')
plt.plot (noise, 'c')

x_train = x[0:80]
y_train = y[0:80]
plt.axvline (x = 60, linewidth = 2, color = 'black')
plt.legend (["Sinal sem ruido", "Sinal coletado", "Ruido gaussiano"], loc = 2, fontsize = 16)

#plt.show ()
plt.savefig ('ruido.png')

for degree in degrees:
  #pylab.figure ()
  degree += 1
  X_train = numpy.column_stack ([numpy.power (x_train,i) for i in range (0,degree)])
  model = numpy.dot (numpy.dot (numpy.linalg.inv (numpy.dot (X_train.transpose (),X_train)),X_train.transpose ()),y_train)
  predicted = numpy.dot (model, [numpy.power (x,i) for i in range (0,degree)])
  train_rmse1 = numpy.sqrt (numpy.sum (numpy.dot (y[0:80] - predicted[0:80], y_train - predicted[0:80])))
  test_rmse1 = numpy.sqrt (numpy.sum (numpy.dot (y[80:] - predicted[80:], y[80:] - predicted[80:])))

  plt.figure ()
  plt.xlabel ('x', fontsize = 24)
  plt.ylabel ('y', fontsize = 24)
  #plt.ylabel ('$2x^2 + x + 2$', fontsize = 24)
  plt.xticks (numpy.arange (-1, 1.2, 0.5))
  plt.yticks (numpy.arange (0, 5.1, 1))
  plt.xticks (fontsize = 16)
  plt.yticks (fontsize = 16)

  #my_dpi = 50
  #plt.figure (figsize= (800/my_dpi, 800/my_dpi), dpi = my_dpi)
  plt.tight_layout ()
  print ("Train RMSE (Degree = " + str (degree - 1) + ")", train_rmse1)
  print ("Test RMSE (Degree = " + str (degree - 1) + ")", test_rmse1)
  #print ("Test RMSE (Degree = 1)", test_rmse1)
  plt.plot (x,y,'g', lw = 3, ls = ':')
  plt.plot (x, predicted,'r')
  plt.axvline (x = 0.60, linewidth = 2, color = 'black')
  #plt.show ()
  plt.legend (["Sinal coletado", "Predito com grau " + str (degree - 1)], loc = 2, fontsize = 16)
  plt.savefig ('grau_' + str (degree - 1) + '.png')


from typing import Optional, Tuple

import numpy as np
import math
import matplotlib.pyplot as plt
from scipy.linalg import lu

def agh_superfast_matrix_multiply(a: np.matrix, b: np.matrix) -> np.matrix:
    """Perform totally ordinary multiplication of matrices.

    :param a: matrix with dimensions _ by _
    :param b: matrix with dimensions _ by _
    :return:  matrix with dimensions _ by _
    """

    (r1, c1) = a.shape
    (r2, c2) = b.shape
    if c1 != r2:
        raise ValueError('Dimensions do not match')
    res = np.asmatrix(np.zeros([r1, c2]))
    for i in range(0, r1):
        for j in range(0, c2):
            for k in range(0, c1):
                res[i, j] += a[i, k] * b[k, j]
    return res

def swapRows(a: np.matrix, i: int, j: int) -> np.matrix:
    r = np.matrix(a)
    r[i, :], r[j, :] = a[j, :], a[i, :]
    return r

def findMaxRow(a: np.matrix, pos: int) -> int:
    (n, _) = a.shape
    max_element = abs(a[pos, pos])
    max_row = pos

    for i in range(pos + 1, n):
        potential = abs(a[i, pos])
        if potential > max_element:
            max_element = potential
            max_row = i

    return max_row

def zero_column(a: np.matrix, pos: int, extended: np.matrix = None) -> np.matrix:
    (n, _) = a.shape
    res = np.matrix(a)

    for i in range(pos + 1, n):
        if res[pos, pos] == 0:
            maxRow = findMaxRow(res, pos)
            res = swapRows(res, pos, maxRow)
            if extended is not None:
                extended = swapRows(extended, pos, maxRow)
            if res[pos, pos] == 0:
                continue

        c = - res[i, pos] / res[pos, pos]
        for j in range(pos, n):
            res[i, j] += c * res[pos, j]

        if extended is not None:
            for j in range(extended.shape[1]):
                extended[i, j] += c * extended[pos, j]

    return res

def agh_superfast_gauss(a: np.matrix, pivot: bool = False, extended: np.matrix = None) -> np.matrix:
    (n1, n2) = a.shape
    if n1 != n2:
        raise ValueError('Only square matrices here')
    n = n1
    res = np.matrix(a, float)

    for i in range(n):
        if pivot:
            maxRow = findMaxRow(res, i)
            res = swapRows(res, i, maxRow)
            if extended is not None:
                extended = swapRows(extended, i, maxRow)
        res = zero_column(res, i, extended)

    return res

def agh_superfast_solve(a: np.matrix, b: np.matrix, pivot: bool = False) -> np.matrix:
    (n1, n2) = a.shape
    (n3, n4) = b.shape
    if n4 != 1 or n1 != n2 or n2 != n3:
        raise ValueError('Matrices dimensions do not match pattern (n, n), (n, 1)')
    n = n1
    y = np.matrix(b, dtype=float)
    g = agh_superfast_gauss(a, pivot, y)
    x = np.asmatrix(np.matrix(np.zeros(n)).transpose())

    for i in range(n - 1, -1, -1):
        x[i, 0] = y[i, 0] / g[i, i]
        for j in range(i - 1, -1, -1):
            y[j, 0] -= g[j, i] * x[i, 0]

    return x

def test_gauss():
    matrices = [
        np.matrix([
            [3, 2, 7, 11],
            [17, 3, 13, 5],
            [7, 23, 11, 17],
            [11, 7, 5, 2]
        ]),
        np.matrix([
            [0, 1, 2],
            [3, 0, 4],
            [5, 6, 7]
        ]),
        np.matrix([[0.0001, -5.0300, 5.8090, 7.8320],
                   [2.2660, 1.9950, 1.2120, 8.0080],
                   [8.8500, 5.6810, 4.5520, 1.3020],
                   [6.7750, -2.253, 2.9080, 3.9700]])
    ]

    for a in matrices:
        _, u = lu(a, permute_l=True)
        a1 = agh_superfast_gauss(a, True)
        print(list(map(np.linalg.det, [a, u, a1])))
        print(a1)
        print("Jest ok" if np.allclose(a1, u) else "Cos nie tak")
        print("-" * 100)

def pivot_matrix(a: np.matrix) -> np.matrix:
    r = np.asmatrix(np.zeros(a.shape))
    for i in range(r.shape[0]):
        maxRow = findMaxRow(a, i)
        r = swapRows(r, maxRow, i)
    return r

def so_lu(a: np.matrix) -> Optional[Tuple[np.matrix, np.matrix]]:
    (n1, n2) = a.shape
    if n1 != n2:
        raise ValueError('Only square matrices here')
    n = n1

    L = np.asmatrix(np.zeros((n, n)))
    U = np.matrix(a)
    np.fill_diagonal(L, 1)  # fill the diagonal of L with 1

    for i in range(n - 1):
        for j in range(i + 1, n):
            L[j, i] = U[j, i] / U[i, i]
            U[j, i] = 0
            for k in range(i + 1, n):
                U[j, k] = U[j, k] - L[j, i] * U[i, k]

    return (L, U)

def agh_superfast_lu(a: np.matrix) -> Optional[Tuple[np.matrix, np.matrix]]:
    (n1, n2) = a.shape
    if n1 != n2:
        raise ValueError('Only square matrices here')
    n = n1
    L = np.asmatrix(np.zeros((n, n)))
    U = np.asmatrix(np.zeros((n, n)))
    P = pivot_matrix(a)
    PA = np.dot(P, a)

    for j in range(n):
        # All diagonal entries of L are set to unity
        L[j, j] = 1.0

        # LaTeX: u_{ij} = a_{ij} - \sum_{k=1}^{i-1} u_{kj} l_{ik}
        for i in range(j + 1):
            s1 = sum(U[k, j] * L[i, k] for k in range(i))
            U[i, j] = P[i, j] - s1

        # LaTeX: l_{ij} = \frac{1}{u_{jj}} (a_{ij} - \sum_{k=1}^{j-1} u_{kj} l_{ik} )
        for i in range(j, n):
            s2 = sum(U[k, j] * L[i, k] for k in range(j))
            L[i, j] = (P[i, j] - s2) / U[j, j]

    return (L, U)

# test_gauss()

A = np.matrix([[0.0001, -5.0300, 5.8090, 7.8320],
               [2.2660, 1.9950, 1.2120, 8.0080],
               [8.8500, 5.6810, 4.5520, 1.3020],
               [6.7750, -2.253, 2.9080, 3.9700]])
b = np.asmatrix(np.matrix([9.5740, 7.2190, 5.7300, 6.2910]).transpose())

(l, u) = so_lu(A)
print(l)
print(u)
(l, u) = lu(A, permute_l=True)
print(l)
print(u)


import numpy as np
import matplotlib.pyplot as plt

k = 12
h = np.zeros(k)
h[0] = 1
h[1] = -0.5*h[0]
h[2] = -0.5*h[1] + 1

for n in range(3,k-1):
		h[n] = -0.5*h[n-1]

#subplots
plt.stem(range(0,k),h)
plt.title('Impulse Response Definition')
plt.xlabel('$n$')
plt.ylabel('$h(n)$')
plt.grid()# minor

plt.savefig('hndef.png')


import numpy as np
import scipy as sp
import scipy.optimize

def analytic_center(A,b,eps,x0,method='nelder-mead'):
    '''
    Input:
    - A   -- N x M
    - b   -- N
    - eps -- N
    - x0  -- M

    Output is a scipy OptimizeResult for

       max np.sum(eps*log(b-A@x))

    optimized using x0 as a starting point

    '''

    def func(x):
        if ((b-A@x)<=0).any():
            return np.inf
        return -np.sum(eps*np.log(b-A@x))

    def jac(x):
        return A.T@(eps/(b-A@x))

    if method=='nelder-mead':
        return sp.optimize.minimize(func,x0,method=method)
    else:
        return sp.optimize.minimize(func,x0,jac=jac,method=method)

def construct_reasonable_rectangle_problem(affines,epsilon=.1,maxsize=300):
    affines=np.require(affines).astype(float)
    n=affines.shape[0]
    assert affines.shape==(n,n)
    maxsize=np.require(maxsize).astype(float)
    if maxsize.shape==():
        maxsize=np.ones(n)*maxsize

    hopefully_feasible=np.ones(n)*5

    affines=np.sign(np.sum(affines,axis=1,keepdims=True)) * affines

    A=np.concatenate([
        -np.eye(n),  # -x <=0
        affines,     # A@x < 1
        np.eye(n),   # x < maxsize
    ],axis=0)
    b=np.concatenate([
        np.zeros(n),
        np.ones(n),
        maxsize
    ],axis=0)

    if not (A@hopefully_feasible<b).all():
        raise ValueError("affine transformation is too big, try another method")

    eps=np.r_[np.ones(n),np.ones(2*n)*epsilon]

    return A,b,eps,hopefully_feasible

def calc_reasonable_rectangles(affines,epsilon=.01,maxsize=300):
    '''
    Input
    - affines -- N
    - epsilon -- scalar
    - maxsize -- scalar or N

    Output
    - rectangle -- N, integer

    Finds a reasonably large value of rectangle so that

      (np.abs(affines@rectangle)<1).all()
      (rectangle<maxsize).all()

    We do this in two steps.  First, we get all the
    affines pointing in the same direction.

        affines=np.sign(np.sum(affines,axis=1,keepdims=True)) * affines

    and then solving the optimization problem

      max_rectangle
          np.sum(np.log(rectangle))
        + epsilon*np.sum(np.log(maxsize-rectangle))
        + epsilon*np.sum(np.log(b-affines@rectangle))
    '''

    A,b,eps,hopefully_feasible = construct_reasonable_rectangle_problem(
        affines,epsilon,maxsize
    )

    rez=analytic_center(A,b,eps,hopefully_feasible,method='nelder-mead')
    return np.floor(rez['x']).astype(int)


"""Huber Loss for Regression or Binary Classification. Robust loss, combination of quadratic loss and linear loss."""
import typing
import numpy as np
from h2oaicore.metrics import CustomScorer
from sklearn.preprocessing import LabelEncoder

# temp. references:
# Wikipedia: https://en.wikipedia.org/wiki/Huber_loss
# https://stackoverflow.com/questions/45006341/xgboost-how-to-use-mae-as-objective-function
# Tukey loss: https://web.as.uky.edu/statistics/users/pbreheny/764-F11/notes/12-1.pdf

class MyHuberLossScorer(CustomScorer):
    '''
    Huber Loss Scorer is a loss function used in robust regression, that is less
    sensitive to outliers in data than the squared error loss. This custom
    scorer supports both regression and binary binary classification problems
    using different formulas and different defaults for delta (see below).
    For more details see: https://en.wikipedia.org/wiki/Huber_loss

    Parameters
    ----------
    delta : numeric
        Hyperparameter with defaults =1.345 for regression and =0.1 for binary
        classification
    '''
    _delta_regression = 1.345
    _delta_binary = 0.1
    _description = "My Huber Loss for Regression or Binary Classification [delta=%f or %f]." % (
        _delta_regression, _delta_binary)
    _binary = True
    _regression = True
    _maximize = False
    _perfect_score = 0
    _display_name = "Huber"

    def score(self,
              actual: np.array,
              predicted: np.array,
              sample_weight: typing.Optional[np.array] = None,
              labels: typing.Optional[np.array] = None) -> float:

        if sample_weight is None:
            sample_weight = np.ones(actual.shape[0])

        isRegression = labels is None
        delta = MyHuberLossScorer._delta_regression if isRegression else MyHuberLossScorer._delta_binary
        if isRegression:
            abs_error = np.abs(np.subtract(actual, predicted))
            loss = np.where(abs_error < delta, .5 * (abs_error) ** 2, delta * (abs_error - 0.5 * delta))
        else:
            lb = LabelEncoder()
            labels = lb.fit_transform(labels)
            actual = lb.transform(actual)
            all0s = np.zeros(actual.shape[0])
            predicted = np.subtract(np.multiply(predicted, 2), 1)
            actual = np.where(actual == 0, -1, 1)
            actual_mult_predict = np.multiply(actual, predicted)
            loss = np.where(actual_mult_predict >= -1,
                            np.square(np.maximum(all0s, np.subtract(1, actual_mult_predict))),
                            -4 * actual_mult_predict)

        loss = np.sum(np.multiply(sample_weight, loss)) / np.sum(sample_weight)
        return float(loss) if actual.shape[0] > 0 else 0


import numpy as np

def sigmoid_sol(x):
    """
    Compute the sigmoid function for the input here.
    """
    ### YOUR CODE HERE
    x = 1. / (1 + np.exp(-x))
    ### END YOUR CODE
    return x

def sigmoid_grad_sol(f):
    """
    Compute the gradient for the sigmoid function here. Note that
    for this implementation, the input f should be the sigmoid
    function value of your original input x.
    """
    ### YOUR CODE HERE
    f = f * (1-f)
    ### END YOUR CODE
    return f


"""1.3.
Un consumidor cuyas preferencias viene representadas por la función de utilidad 𝑈(𝑥, 𝑦) = 3𝑙𝑛 𝑥 + 𝑙𝑛 𝑦, desea maximizar la utilidad que obtiene del consumo de ambos bienes. Siendo los precios a los que se enfrenta px = py = $3  y disponiendo de unos ingresos de $120, ¿cuáles serían las cantidades óptimas a consumir?
"""

import sympy as sp
sp.init_printing()
import funcionesAuxiliares as aux

m, x, y, px, py, t = sp.symbols('m,x,y,px,py,t')
Presupuesto = m - px*x - py*y
Utilidad = 3*sp.log(x) + sp.log(y)

im = 120
ipx = ipy = 3

ej13 = aux.maximizarRestriccion(Utilidad,
                         Presupuesto,
                         im,
                         ipx,
                         ipy )

ej13


import numpy as np

def si_sdr(reference, estimation):
    """
    Scale-Invariant Signal-to-Distortion Ratio (SI-SDR)

    Args:
        reference: numpy.ndarray, [..., T]
        estimation: numpy.ndarray, [..., T]

    Returns:
        SI-SDR

    [1] SDR– Half- Baked or Well Done?
    http://www.merl.com/publications/docs/TR2019-013.pdf

    >>> np.random.seed(0)
    >>> reference = np.random.randn(100)
    >>> si_sdr(reference, reference)
    inf
    >>> si_sdr(reference, reference * 2)
    inf
    >>> si_sdr(reference, np.flip(reference))
    -25.127672346460717
    >>> si_sdr(reference, reference + np.flip(reference))
    0.481070445785553
    >>> si_sdr(reference, reference + 0.5)
    6.3704606032577304
    >>> si_sdr(reference, reference * 2 + 1)
    6.3704606032577304
    >>> si_sdr([1., 0], [0., 0])  # never predict only zeros
    nan
    >>> si_sdr([reference, reference], [reference * 2 + 1, reference * 1 + 0.5])
    array([6.3704606, 6.3704606])

    """
    estimation, reference = np.broadcast_arrays(estimation, reference)

    assert reference.dtype == np.float64, reference.dtype
    assert estimation.dtype == np.float64, estimation.dtype

    reference_energy = np.sum(reference ** 2, axis=-1, keepdims=True)

    # This is $\alpha$ after Equation (3) in [1].
    optimal_scaling = np.sum(reference * estimation, axis=-1, keepdims=True) \
        / reference_energy

    # This is $e_{\text{target}}$ in Equation (4) in [1].
    projection = optimal_scaling * reference

    # This is $e_{\text{res}}$ in Equation (4) in [1].
    noise = estimation - projection

    ratio = np.sum(projection ** 2, axis=-1) / np.sum(noise ** 2, axis=-1)
    return 10 * np.log10(ratio)