import numpy as np
from random import random, uniform
import matplotlib.pyplot as plt

def ackley(x, a=20, b=0.2, c=2*np.pi):
    # n dimensional
    x = np.asarray_chkfinite(x)  # ValueError if any NaN or Inf
    n = len(x)
    s1 = sum(x**2)
    s2 = sum(np.cos(c * x))
    return -a*np.exp(-b*np.sqrt(s1 / n)) - np.exp(s2 / n) + a + np.exp(1)

def bukin(x):
    # 2 dimensional
    return 100 * np.sqrt(np.abs(x[1] - 0.01 * x[0] ** 2)) + 0.01 * np.abs(x[0] + 10)

def griewank(x):
    # n dimensional
    dim = len(x)
    j = np.arange(1, dim + 1)
    sq_list = [i ** 2 for i in x]
    s = sum(sq_list)
    p = np.prod(np.cos(x / np.sqrt(j)))
    return s / 4000 - p + 1

def rosenbrock(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    x0 = x[:-1]
    x1 = x[1:]
    return sum(100 * (x1 - x0 ** 2) ** 2 + (x0 - 1) ** 2)

def zakharov(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    dim = len(x)
    j = np.arange(1, dim + 1)
    s1 = sum(0.5 * j * x)
    return sum(x ** 2) + s1 ** 2 + s1 ** 4

def levy(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    w = 1 + (x - 1) / 4
    return (np.sin(np.pi * w[0])) ** 2 \
        + sum((w[:-1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[:-1] + 1) ** 2)) \
        + (w[-1] - 1) ** 2 * (1 + (np.sin(2 * np.pi * w[-1]) ** 2))

def rastrigin(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    dim = len(x)
    return 10 * dim + sum(x ** 2 - 10 * np.cos(2 * np.pi * x))

def schwefel(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    dim = len(x)
    return 418.9829 * dim - sum(x * np.sin(np.sqrt(np.abs(x))))

def sphere(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    return sum(x ** 2)

def sum_diff_powers(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    dim = len(x)
    j = np.arange(1, dim + 1)
    return sum(np.abs(x) ** (j + 1))

def sum_of_squares(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    dim = len(x)
    j = np.arange(1, dim + 1)
    return sum(j * x ** 2)

def rotated_hyper_ellipsoid(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    dim = len(x)
    total = 0
    for i in range(1, dim + 1):
        for j in range(1, i + 1):
            total += (x[j - 1] ** 2)
    return total

def ellipse(x):
    # n dimensional
    x = np.asarray_chkfinite(x)
    return np.mean((1 - x) ** 2) + 100 * np.mean(np.diff(x) ** 2)

def schafferN6(x):
    # 2 dimensional
    x = np.asarray_chkfinite(x)
    num = np.sin(np.sqrt(x[0] ** 2 + x[1] ** 2)) ** 2 - 0.5
    denom = (1 + 0.001*(x[0] ** 2 + x[1] ** 2)) ** 2
    return 0.5 + num / denom

def simple_parabola(x):
    x = np.asarray(x)
    return -3 * x ** 2 + 3

def beamline_test_function(x):
    x = np.asarray(x)
    return np.sin(4 * x) - np.cos(8 * x) + 2

def ensure_bounds(vec, bounds):
    # Makes sure each individual stays within bounds and adjusts them if they aren't
    vec_new = []
    # cycle through each variable in vector
    for i in range(len(vec)):
        # variable exceeds the minimum boundary
        if vec[i] < bounds[i][0]:
            vec_new.append(bounds[i][0])
        # variable exceeds the maximum boundary
        if vec[i] > bounds[i][1]:
            vec_new.append(bounds[i][1])
        # the variable is fine
        if bounds[i][0] <= vec[i] <= bounds[i][1]:
            vec_new.append(vec[i])
    return vec_new

def omea(positions, func):
    evaluations = []
    min_positions = []
    min_evals = []
    # get first position eval
    evaluations.append(func(positions[0]))

    for i in range(1, len(positions)):
        hold_eval = []
        in_between = np.linspace(positions[i - 1], positions[i], 50)
        between = in_between[1:-1]
        for t in range(len(between)):
            hold_eval.append(func(between[t]))
        # get eval of next position
        evaluations.append(func(positions[i]))
        # find index of min
        ii = np.argmin(hold_eval)
        min_positions.append(between[ii])
        min_evals.append(hold_eval[ii])
    for i in range(len(min_positions)):
        if min_evals[i] < evaluations[i + 1]:
            evaluations[i + 1] = min_evals[i]
            for k in range(len(min_positions[i])):
                positions[i + 1][k] = min_positions[i][k]
    return positions, evaluations

def rand_1(pop, popsize, t_indx, mut, bounds):
    # v = x_r1 + F * (x_r2 - x_r3)
    idxs = [idx for idx in range(popsize) if idx != t_indx]
    a, b, c = np.random.choice(idxs, 3, replace=False)
    x_1 = pop[a]
    x_2 = pop[b]
    x_3 = pop[c]

    x_diff = [x_2_i - x_3_i for x_2_i, x_3_i in zip(x_2, x_3)]
    v_donor = [x_1_i + mut * x_diff_i for x_1_i, x_diff_i in zip(x_1, x_diff)]
    v_donor = ensure_bounds(v_donor, bounds)
    return v_donor

def best_1(pop, popsize, t_indx, mut, bounds, ind_sol):
    # v = x_best + F * (x_r1 - x_r2)
    x_best = pop[ind_sol.index(np.min(ind_sol))]
    idxs = [idx for idx in range(popsize) if idx != t_indx]
    a, b = np.random.choice(idxs, 2, replace=False)
    x_1 = pop[a]
    x_2 = pop[b]

    x_diff = [x_1_i - x_2_i for x_1_i, x_2_i in zip(x_1, x_2)]
    v_donor = [x_b + mut * x_diff_i for x_b, x_diff_i in zip(x_best, x_diff)]
    v_donor = ensure_bounds(v_donor, bounds)
    return v_donor

def current_to_best_1(pop, popsize, t_indx, mut, bounds, ind_sol):
    # v = x_curr + F * (x_best - x_curr) + F * (x_r1 - r_r2)
    x_best = pop[ind_sol.index(np.min(ind_sol))]
    idxs = [idx for idx in range(popsize) if idx != t_indx]
    a, b = np.random.choice(idxs, 2, replace=False)
    x_1 = pop[a]
    x_2 = pop[b]
    x_curr = pop[t_indx]

    x_diff1 = [x_b - x_c for x_b, x_c in zip(x_best, x_curr)]
    x_diff2 = [x_1_i - x_2_i for x_1_i, x_2_i in zip(x_1, x_2)]
    v_donor = [x_c + mut * x_diff_1 + mut * x_diff_2 for x_c, x_diff_1, x_diff_2
               in zip(x_curr, x_diff1, x_diff2)]
    v_donor = ensure_bounds(v_donor, bounds)
    return v_donor

def best_2(pop, popsize, t_indx, mut, bounds, ind_sol):
    # v = x_best + F * (x_r1 - x_r2) + F * (x_r3 - r_r4)
    x_best = pop[ind_sol.index(np.min(ind_sol))]
    idxs = [idx for idx in range(popsize) if idx != t_indx]
    a, b, c, d = np.random.choice(idxs, 4, replace=False)
    x_1 = pop[a]
    x_2 = pop[b]
    x_3 = pop[c]
    x_4 = pop[d]

    x_diff1 = [x_1_i - x_2_i for x_1_i, x_2_i in zip(x_1, x_2)]
    x_diff2 = [x_3_i - x_4_i for x_3_i, x_4_i in zip(x_3, x_4)]
    v_donor = [x_b + mut * x_diff_1 + mut * x_diff_2 for x_b, x_diff_1, x_diff_2
               in zip(x_best, x_diff1, x_diff2)]
    v_donor = ensure_bounds(v_donor, bounds)
    return v_donor

def rand_2(pop, popsize, t_indx, mut, bounds):
    # v = x_r1 + F * (x_r2 - x_r3) + F * (x_r4 - r_r5)
    idxs = [idx for idx in range(popsize) if idx != t_indx]
    a, b, c, d, e = np.random.choice(idxs, 5, replace=False)
    x_1 = pop[a]
    x_2 = pop[b]
    x_3 = pop[c]
    x_4 = pop[d]
    x_5 = pop[e]

    x_diff1 = [x_2_i - x_3_i for x_2_i, x_3_i in zip(x_2, x_3)]
    x_diff2 = [x_4_i - x_5_i for x_4_i, x_5_i in zip(x_4, x_5)]
    v_donor = [x_1_i + mut * x_diff_1 + mut * x_diff_2 for x_1_i, x_diff_1, x_diff_2
               in zip(x_1, x_diff1, x_diff2)]
    v_donor = ensure_bounds(v_donor, bounds)
    return v_donor

def mutate(population, strategy, mut, bounds, ind_sol):
    mutated_indv = []
    for i in range(len(population)):
        if strategy == 'rand/1':
            v_donor = rand_1(population, len(population), i, mut, bounds)
        elif strategy == 'best/1':
            v_donor = best_1(population, len(population), i, mut, bounds, ind_sol)
        elif strategy == 'current-to-best/1':
            v_donor = current_to_best_1(population, len(population), i, mut, bounds, ind_sol)
        elif strategy == 'best/2':
            v_donor = best_2(population, len(population), i, mut, bounds, ind_sol)
        elif strategy == 'rand/2':
            v_donor = rand_2(population, len(population), i, mut, bounds)
        mutated_indv.append(v_donor)
    return mutated_indv

def crossover(population, mutated_indv, crosspb):
    crossover_indv = []
    for i in range(len(population)):
        v_trial = []
        x_t = population[i]
        for j in range(len(x_t)):
            crossover_val = random()
            if crossover_val <= crosspb:
                v_trial.append(mutated_indv[i][j])
            else:
                v_trial.append(x_t[j])
        crossover_indv.append(v_trial)
    return crossover_indv

def select(population, crossover_indv, ind_sol, func):
    positions = [elm for elm in crossover_indv]
    positions.insert(0, population[0])
    positions, evals = omea(positions, func)
    positions = positions[1:]
    evals = evals[1:]
    for i in range(len(evals)):
        if evals[i] < ind_sol[i]:
            population[i] = positions[i]
            ind_sol[i] = evals[i]
    population.reverse()
    ind_sol.reverse()
    return population, ind_sol

def diff_ev(bounds, func, threshold, popsize=10, crosspb=0.8, mut=0.05, mut_type='rand/1'):
    # Initial population
    population = []
    best_fitness = [10]
    for i in range(popsize):
        indv = []
        for j in range(len(bounds)):
            indv.append(uniform(bounds[j][0], bounds[j][1]))
        population.append(indv)
    init_pop = population[:]

    # Evaluate fitness/OMEA
    init_pop.sort()
    pop, ind_sol = omea(init_pop, func)
    # reverse for efficiency with motors
    pop.reverse()
    ind_sol.reverse()

    # Termination conditions
    v = 0  # generation number
    consec_best_ctr = 0  # counting successive generations with no change to best value
    old_best_fit_val = 0
    while not (consec_best_ctr >= 5 and old_best_fit_val <= threshold):
        print('\nGENERATION ' + str(v + 1))
        best_gen_sol = []  # holding best scores of each generation
        mutated_trial_pop = mutate(pop, mut_type, mut, bounds, ind_sol)
        cross_trial_pop = crossover(pop, mutated_trial_pop, crosspb)
        pop, ind_sol = select(pop, cross_trial_pop, ind_sol, func)

        # score keeping
        gen_best = np.min(ind_sol)  # fitness of best individual
        best_indv = pop[ind_sol.index(gen_best)]  # best individual positions
        best_gen_sol.append(best_indv)
        best_fitness.append(gen_best)

        print('      > BEST FITNESS:', gen_best)
        print('         > BEST POSITIONS:', best_indv)

        v += 1
        if np.round(gen_best, 6) == np.round(old_best_fit_val, 6):
            consec_best_ctr += 1
            print('Counter:', consec_best_ctr)
        else:
            consec_best_ctr = 0
        old_best_fit_val = gen_best

        if consec_best_ctr >= 5 and old_best_fit_val <= threshold:
            print('Finished')
            break
        else:
            change_index = ind_sol.index(np.max(ind_sol))
            changed_indv = pop[change_index]
            for k in range(len(changed_indv)):
                changed_indv[k] = uniform(bounds[k][0], bounds[k][1])
            # OMEA would be here too
            # not sure how to do this with functions
            ind_sol[change_index] = func(changed_indv)

    x_best = best_gen_sol[-1]
    print('\nThe best individual is', x_best, 'with a fitness of', gen_best)
    print('It took', v, 'generations')

    # plot best fitness
    plot_index = np.arange(len(best_fitness))
    plt.figure()
    plt.plot(plot_index, best_fitness)

diff_ev(bounds=[(-10, 10)] * 20, func=sphere, threshold=0.05, popsize=10, crosspb=0.8, mut=0.2)


import torch
import torch.nn as nn
import numpy as np

#
#        -> 2.0              -> 0.65
# Linear -> 1.0  -> Softmax  -> 0.25   -> CrossEntropy(y, y_hat)
#        -> 0.1              -> 0.1
#
#     scores(logits)      probabilities
#                           sum = 1.0
#

# Softmax applies the exponential function to each element, and normalizes
# by dividing by the sum of all these exponentials
# -> squashes the output to be between 0 and 1 = probability
# sum of all probabilities is 1
def softmax(x):
    return np.exp(x) / np.sum(np.exp(x), axis=0)

x = np.array([2.0, 1.0, 0.1])
outputs = softmax(x)
print('softmax numpy:', outputs)

x = torch.tensor([2.0, 1.0, 0.1])
outputs = torch.softmax(x, dim=0) # along values along first axis
print('softmax torch:', outputs)

# Cross entropy
# Cross-entropy loss, or log loss, measures the performance of a classification model
# whose output is a probability value between 0 and 1.
# -> loss increases as the predicted probability diverges from the actual label
def cross_entropy(actual, predicted):
    EPS = 1e-15
    predicted = np.clip(predicted, EPS, 1 - EPS)
    loss = -np.sum(actual * np.log(predicted))
    return loss # / float(predicted.shape[0])

# y must be one hot encoded
# if class 0: [1 0 0]
# if class 1: [0 1 0]
# if class 2: [0 0 1]
Y = np.array([1, 0, 0])
Y_pred_good = np.array([0.7, 0.2, 0.1])
Y_pred_bad = np.array([0.1, 0.3, 0.6])
l1 = cross_entropy(Y, Y_pred_good)
l2 = cross_entropy(Y, Y_pred_bad)
print(f'Loss1 numpy: {l1:.4f}')
print(f'Loss2 numpy: {l2:.4f}')

# CrossEntropyLoss in PyTorch (applies Softmax)
# nn.LogSoftmax + nn.NLLLoss  #注意已经是两个功能集合了
# NLLLoss = negative log likelihood loss
loss = nn.CrossEntropyLoss()
# loss(input, target)

# target is of size nSamples = 1
# each element has class label: 0, 1, or 2
# Y (=target) contains class labels, not one-hot
Y = torch.tensor([0])

# input is of size nSamples x nClasses = 1 x 3
# y_pred (=input) must be raw, unnormalizes scores (logits) for each class, not softmax
Y_pred_good = torch.tensor([[2.0, 1.0, 0.1]])
Y_pred_bad = torch.tensor([[0.5, 2.0, 0.3]])
l1 = loss(Y_pred_good, Y)
l2 = loss(Y_pred_bad, Y)

print(f'PyTorch Loss1: {l1.item():.4f}')
print(f'PyTorch Loss2: {l2.item():.4f}')

# get predictions
_, predictions1 = torch.max(Y_pred_good, 1)
_, predictions2 = torch.max(Y_pred_bad, 1)
print(f'Actual class: {Y.item()}, Y_pred1: {predictions1.item()}, Y_pred2: {predictions2.item()}')

# allows batch loss for multiple samples

# target is of size nBatch = 3
# each element has class label: 0, 1, or 2
Y = torch.tensor([2, 0, 1])  # 注意这里是3个sample

# input is of size nBatch x nClasses = 3 x 3
# Y_pred are logits (not softmax)
Y_pred_good = torch.tensor(
    [[0.1, 0.2, 3.9], # predict class 2
    [1.2, 0.1, 0.3], # predict class 0
    [0.3, 2.2, 0.2]]) # predict class 1

Y_pred_bad = torch.tensor(
    [[0.9, 0.2, 0.1],
    [0.1, 0.3, 1.5],
    [1.2, 0.2, 0.5]])

l1 = loss(Y_pred_good, Y)
l2 = loss(Y_pred_bad, Y)
print(f'Batch Loss1:  {l1.item():.4f}')
print(f'Batch Loss2: {l2.item():.4f}')

# get predictions
_, predictions1 = torch.max(Y_pred_good, 1)
_, predictions2 = torch.max(Y_pred_bad, 1)
print(f'Actual class: {Y}, Y_pred1: {predictions1}, Y_pred2: {predictions2}')

# Binary classification
class NeuralNet1(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(NeuralNet1, self).__init__()
        self.linear1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.linear2 = nn.Linear(hidden_size, 1)

    def forward(self, x):
        out = self.linear1(x)
        out = self.relu(out)
        out = self.linear2(out)
        # sigmoid at the end
        y_pred = torch.sigmoid(out)
        return y_pred

model = NeuralNet1(input_size=28*28, hidden_size=5)
criterion = nn.BCELoss()  #注意loss function的不同

# Multiclass problem
class NeuralNet2(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(NeuralNet2, self).__init__()
        self.linear1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.linear2 = nn.Linear(hidden_size, num_classes)

    def forward(self, x):
        out = self.linear1(x)
        out = self.relu(out)
        out = self.linear2(out)
        # no softmax at the end
        return out

model = NeuralNet2(input_size=28*28, hidden_size=5, num_classes=3)
criterion = nn.CrossEntropyLoss()  # (applies Softmax)


#ZADANIE 3
#Oblicz i przedstaw odchylenia standardowe bledow dla wszystkich wariantow z zadania 2.
#Przedstaw wnioski.

import numpy as np

def tbernoulli(n):
    if n<0 and n>20:
        return 0
    else:
        temp=[1, 1/2, 1/6, 0, -1/30, 0, 1/42, 0, -1/30, 0, 5/66, 0, -691/2730, 0, 7/6, 0, -3617/510, 0, 43867/798, 0, -174611/330]
        return temp[n]

def silnia(x):
    sil = 1
    while x > 0:
        sil = sil * x
        x = x - 1
    return sil

def tsin(x):
    sum_sin=0
    for i in range(0,6):
        sum_sin=sum_sin+((-1)**i)*(x**(2*i+1))/float(silnia(2*i+1))
    return sum_sin

def tcos(x):
    sum_cos=0
    for i in range(0,6):
        sum_cos=sum_cos+((-1)**i)*(x**(2*i))/float(silnia(2*i))
    return sum_cos

def texp(x):
    sum_exp=0
    for i in range(0,6):
        sum_exp=sum_exp+(x**i)/float(silnia(i))
    return sum_exp

def ttan(x):
    sum_tan=0
    for i in range(0,6):
        sum_tan=sum_tan+tbernoulli(2*i)*((-4)**i)*(1-(4**i))*(x**(2*i-1))/float(silnia(2*i))
    return sum_tan

def blad_bw_std(f1,f2,xp,xk,k):
    bledy=[np.abs(f1(i)-f2(i)) for i in np.arange(xp,xk,k)]
    return np.std(bledy)

print("Sin")
print(blad_bw_std(tsin,np.sin,-3*np.pi,0,0.1))
print(blad_bw_std(tsin,np.sin,0,np.pi/4,0.1))
print(blad_bw_std(tsin,np.sin,-3*np.pi,3*np.pi,0.1))

print("\nCos")
print(blad_bw_std(tcos,np.cos,-3*np.pi,0,0.1))
print(blad_bw_std(tcos,np.cos,0,np.pi/4,0.1))
print(blad_bw_std(tcos,np.cos,-3*np.pi,3*np.pi,0.1))

print("\nExp")
print(blad_bw_std(texp,np.exp,0,20,0.1))
print(blad_bw_std(texp,np.exp,0,1,0.1))

print("\nTan")
print(blad_bw_std(ttan,np.tan,-np.pi,2,0.1))
print(blad_bw_std(ttan,np.tan,0.1,np.pi/2,0.1))
print(blad_bw_std(ttan,np.tan,-np.pi/2,np.pi/2,0.1))

#Jezeli zakres danych jest niewielki to algorytm dziala poprawnie.
#Im wiekszy zakres danych tym wieksze bledy.
#Przy szerokim zakresie danych bledy sa bardzo duze.


"""
Calculates surface rougness based on slope standard deviation

Many rougness metrics are based on elevation differences. This could lead to a
flat, sloped area being considered rough, while a surface with many small
elevation changes (like very coarse sandpaper) woul be smooth. Is this really
what roughness means. Instead I'm interested in deriving roughness from
slope standard deviation

I'm not the first person to do this, I think Grohmann et al. 2010 was
(10.1109/TGRS.2010.2053546)
"""

import rasterio as rs
import numpy as np
from scipy import ndimage

def slope_stdev(dem, neighborhood=3):
    # DEM is a 2D array of elevation values, neighborhood indicates the size of
    # the square neighborhood for slope variance calculations
    # i.e. neighborhood = 3 means a 3x3 neighborhood
    gradX, gradY = np.gradient(dem)
    slope = np.sqrt(gradX ** 2 + gradY ** 2)
    slopeMean = ndimage.uniform_filter(slope,neighborhood)
    slopeSqrMean = ndimage.uniform_filter(slope**2,neighborhood)
    slopeVar = slopeSqrMean - slopeMean**2
    slopeStdev = np.sqrt(np.absolute(slopeVar)) #In perfectly flat areas where
    #variance is ideally zero, float precision can result in a number slightly
    #below zero. Using the absolute value of variance prevents nonreal results
    return slopeStdev

def sd_slope(input_path, output_path, neighborhood=3, interpMethod='linear'):
    dataset = rs.open(input_path)
    band1 = dataset.read(1)
    kwds = dataset.profile
    slopeStdev = slope_stdev(band1,neighborhood)

    kwds['dtype'] = slopeStdev.dtype
    with rs.open(output_path, 'w', **kwds) as dst:
        dst.write(slopeStdev, indexes=1)
    return


import numpy as np
from scipy.special import iv

def tapering_window(time,D,mywindow):

	""" tapering_window returns the window for tapering a WOSA segment.
		Inputs:
		- time [1-dim numpy array of floats]: times along the WOSA segment.
		- D [float]: Temporal length of the WOSA segment.
		- mywindow [int]: Choice of tapering window:
			-> 1: Square window
			-> 2: Triangular window
			-> 3: sin window
			-> 4: sin**2 (Hanning) window
			-> 5: sin**3 window
			-> 6: sin**4 window
			-> 7: Hamming window, defined as 0.54-0.46*np.cos(2.0*np.pi*time/D)
			-> 8: 4-term Blackman-Harris window, with a0=0.35875 and a1=0.48829 and a2=0.14128 and a3=0.01168
			-> 9: Kaiser-Bessel window, with parameter alpha=2.5
			-> 10: Gaussian window, with standard dev. sigma=D/6.0
		The terminology and formulas come from:
		F. Harris. On the use of windows for harmonic analysis with the discrete fourier transform. Proceedings of the IEEE, 66(1):51-83, January 1978.
		WARNING: Provide the vector 'time' such that for all k=0,...,time.size-1, we have time[k]>=0 and time[k]<=D
		Outputs:
		- tapering_window [1-dim numpy array of floats - size=time.size]: the tapering window.
		-----------------------------
		This is part of WAVEPAL
		(C) 2016 G. Lenoir"""

	T=time.size
	if mywindow==1:
		tapering_window=np.ones(T)
	elif mywindow==2:
		tapering_window=1.0-np.absolute(time-D/2.0)/(D/2.0)
	elif mywindow==3:
		tapering_window=np.sin(np.pi*time/D)
	elif mywindow==4:
		tapering_window=(np.sin(np.pi*time/D))**2
	elif mywindow==5:
		tapering_window=(np.sin(np.pi*time/D))**3
	elif mywindow==6:
		tapering_window=(np.sin(np.pi*time/D))**4
	elif mywindow==7:
		tapering_window=0.54-0.46*np.cos(2.0*np.pi*time/D)
	elif mywindow==8:
		a0=0.35875
		a1=0.48829
		a2=0.14128
		a3=0.01168
		tapering_window=a0-a1*np.cos(2.0*np.pi*time/D)+a2*np.cos(4.0*np.pi*time/D)-a3*np.cos(6.0*np.pi*time/D)
	elif mywindow==9:
		alpha=2.5
		tapering_window=iv(0,np.pi*alpha*np.sqrt(1.0-((time-D/2.0)/(D/2.0))**2))
	elif mywindow==10:
		sig=D/6.0
		tapering_window=np.exp(-(time-D/2.0)**2/2.0/sig**2)
	else:
		print "Error: The window number you entered is not valid. Check input variable 'mywindow'."
		return
	return tapering_window


import math
import scipy as sp
import scipy.stats

####Detecting the overlapping and hierarchical community structure in complex networks
####Normalized Mutual Information to evaluate overlapping community finding algorithms
logBase = 2

def partialEntropyAProba(proba):
    if proba==0:
        return 0
    return -proba * math.log(proba,logBase)

def entropyFracSet(fracSet):
    toReturn = 0
    for frac in fracSet:
        toReturn+=partialEntropyAProba(frac)
    return toReturn

def comPairConditionalEntropy(cl,clKnown,allNodes): #cl1,cl2, snapshot_communities (set of nodes)
    #H(Xi|Yj ) =H(Xi, Yj ) − H(Yj )
    # h(a,n) + h(b,n) + h(c,n) + h(d,n)
    # −h(b + d, n)−h(a + c, n)
    #a: count agreeing on not belonging
    #b: count disagreeing : not in 1 but in 2
    #c: count disagreeing : not in 2 but in 1
    #d: count agreeing on belonging
    nbNodes = len(allNodes)

    a =len((allNodes - cl) - clKnown)/nbNodes
    b = len(clKnown-cl)/nbNodes
    c = len(cl-clKnown)/nbNodes
    d = len(cl & clKnown)/nbNodes

    if partialEntropyAProba(a)+partialEntropyAProba(d)>partialEntropyAProba(b)+partialEntropyAProba(c):
        entropyKnown=sp.stats.entropy([len(clKnown)/nbNodes,1-len(clKnown)/nbNodes],base=logBase)
        conditionalEntropy = sp.stats.entropy([a,b,c,d],base=logBase) - entropyKnown
        #print("normal",entropyKnown,sp.stats.entropy([a,b,c,d],base=logBase))
    else:
        conditionalEntropy = sp.stats.entropy([len(cl)/nbNodes,1-len(cl)/nbNodes],base=logBase)
    #print("abcd",a,b,c,d,conditionalEntropy,cl,clKnown)

    return conditionalEntropy #*nbNodes

def coverEntropy(cover,allNodes): #cover is a list of set, no com ID
    allEntr = []
    for com in cover:
        fractionIn = len(com)/len(allNodes)
        allEntr.append(sp.stats.entropy([fractionIn,1-fractionIn],base=logBase))

    return sum(allEntr)

def coverConditionalEntropy(cover,coverRef,allNodes): #cover and coverRef and list of set
    X=cover
    Y=coverRef

    allMatches = []
    #print(cover)
    #print(coverRef)
    for com in cover:
        matches = [(com2,comPairConditionalEntropy(com,com2,allNodes)) for com2 in coverRef]
        bestMatch = min(matches,key=lambda c: c[1])
        allMatches.append(bestMatch[1])
    #print(allMatches)
    return sum(allMatches)

def NMI(cover,coverRef,allNodes=-1,variant="LFR",adjustForChance=False): #cover and coverRef should be list of set, no community ID
    """
    :param cover: set of set of nodes
    :param coverRef:set of set of nodes
    :param allNodes:
    :param variant:
    :param adjustForChance:
    :return:
    """
    if (len(cover)==0 and len(coverRef)!=0) or (len(cover)!=0 and len(coverRef)==0):
        return 0
    if cover==coverRef:
        return 1

    if allNodes==-1:
        allNodes={n for c in coverRef for n in c}
        allNodes|={n for c in cover for n in c}

    #print("compute HXY")
    HXY = coverConditionalEntropy(cover,coverRef,allNodes)
    #print("compute HYX")

    HYX = coverConditionalEntropy(coverRef,cover,allNodes)
    HX = coverEntropy(cover,allNodes)
    #print("HY")
    HY = coverEntropy(coverRef,allNodes)

    #print("HXY...",HXY,HYX,HX,HY)
    NMI = -10
    if variant=="LFR":
        NMI = 1- 0.5*(HXY/HX+HYX/HY)
    elif variant=="MGH":
        IXY = 0.5*(HX-HXY+HY-HYX)
        NMI =  IXY/(max(HX,HY))
    if NMI<0 or NMI>1 or math.isnan(NMI):
        print("NMI: %s  from %s %s %s %s "%(NMI,HXY,HYX,HX,HY))
        raise Exception("incorrect NMI")
    return NMI

def NMIdynamic(covers,coversRef,allNodes=-1,variant="LFR", symmetric=True): #covers,coversRef : ordered list of cover type:(dict/bidic com:set(nodes))
    combinedCover = {}
    combinedCoverRef = {}

    for t in range(len(coversRef)): #for each slice
        #if len(coversRef[t])==0:

        for c in covers[t]: #for each com:
            combinedCover.setdefault(c,set())
            combinedCover[c]|= {(t,n) for n in covers[t][c]}
        for c in coversRef[t]:  # for each com:
            combinedCoverRef.setdefault(c, set())
            combinedCoverRef[c] |= {(t, n) for n in coversRef[t][c]}

    if not symmetric:
        acceptableNodes = set()
        for c in combinedCoverRef:
            acceptableNodes |= {n for n in combinedCoverRef[c]}
        for c in combinedCover:
            combinedCover[c] = combinedCover[c] & acceptableNodes
        combinedCover = {c:v for c,v in combinedCover.items() if len(v)>0}

    if len(combinedCoverRef)>0: # if there is no com in the reference, we cannot compute NMI
        if len(combinedCover)==0: # if the set of tested coms is empty, its NMI value is 0
            return 0
        return NMI(combinedCover.values(),combinedCoverRef.values(),allNodes,variant=variant)
    else:
        return None


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

def get_data(filename,frac):

	data = pd.read_csv(filename)
	LABEL = 'MEDV'

	data['x0'] = np.array([1 for i in range(len(data.index))])

	test_data = data.sample(frac = frac)
	train_data = data.drop(test_data.index)

	from sklearn import preprocessing as prp

	y_test = np.array(test_data[LABEL])
	y_test = y_test.reshape((len(y_test),1))   # MAKING it 2 dimensional for tensorflow
	x_test = test_data.drop(LABEL,axis = 1).values
	std_scale = prp.StandardScaler().fit(x_test)
	x_test = std_scale.transform(x_test)

	y_train = np.array(train_data[LABEL])
	y_train = y_train.reshape((len(y_train),1))
	x_train = train_data.drop(LABEL,axis = 1).values
	std_scale = prp.StandardScaler().fit(x_train)
	x_train = std_scale.transform(x_train)

	return x_test, y_test, x_train, y_train

filename = "BostonHousingPrices/Boston_Housing_Prices.csv"

x_test, y_test, x_train, y_train = get_data(filename,frac = 0.3)

from sklearn.linear_model import LinearRegression
regr = LinearRegression(fit_intercept = True)
regr.fit(x_train,y_train)
wsk = regr.coef_

n_samples,n_features = x_test.shape

# HYPERPARAMETER

BATCH_SIZE = 1
ETA = 0.0003
epochs = 1000

X_TRAIN = tf.placeholder(tf.float64,shape = (None,n_features),name = "train_data")
Y_TRAIN = tf.placeholder(tf.float64,shape = (None,1), name = "train_labels")

W = tf.Variable(np.random.randn(n_features,1),name = "weights")
# B = tf.Variable(np.random.randn(None,1),name = "biases")

prediction = tf.matmul(X_TRAIN,W)
cost = tf.reduce_sum(tf.pow(Y_TRAIN-prediction,2))/2

optimizer = tf.train.GradientDescentOptimizer(ETA).minimize(cost)
init_op = tf.initialize_all_variables()

log = []

with tf.Session() as sess:

	sess.run(init_op)

	for epoch in range(epochs):

		for x,y in zip(x_train,y_train):
			sess.run(optimizer,feed_dict = {X_TRAIN:x.reshape(((x.size/n_features),n_features)),Y_TRAIN:y.reshape((y.size,1))})

		log.append(sess.run(cost,feed_dict = {X_TRAIN:x_train,Y_TRAIN:y_train}))
		# print("Epoch: ",epoch,"    cost: ",log[epoch])

	print("Optimization finished\n")

	weights = sess.run(W)
	print("Weights:  ",weights)
	print("sklearn weights: ",wsk)
	test_prediction = sess.run(tf.matmul(x_test[0].reshape((1,n_features)),weights))
	print("\n\n",test_prediction," ",y_test[0])
	print(regr.predict(x_test[0]))

plt.plot([i for i in range(epochs)],log)
plt.show()


import plotly.graph_objects as go
import numpy as np

# Changing K plot
# Create figure
fig_k = go.Figure()

# Add traces, one for each slider step
k= np.linspace(-5, 5, 21)
phi_0 = 0
sig = 1
x=np.arange(-5, 5.05, 0.05)

for k_i in np.arange(-5, 5, 0.5):
    psi = np.multiply(np.exp(1j*k_i*x), np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi))))
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=x,
            y=np.imag(psi),
            z=np.real(psi),
            mode="lines"))
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=x,
            y=-np.ones(psi.shape),
            z=np.real(psi),
            mode="lines"))
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= x,
            y= np.imag(psi),
            z= -np.ones(psi.shape),
            mode="lines"))

x2 = np.arange(-5, 5.05, 0.05)
y2 =-np.ones(x2.shape)
z2 = np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi)))
#fig2 = px.line_3d(df2, x="x", y="y", z="z",color="z")
fig_k.add_trace(go.Scatter3d(visible=True, x=x2, y=y2, z=z2, name="prob amplitude", mode="lines", line=dict(color='black', width=2)))
# Make 10th trace visible
fig_k.data[30].visible = True
fig_k.data[31].visible = True
fig_k.data[32].visible = True

# Create and add slider
steps = []
for i in range(0, len(fig_k.data)-1, 3):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig_k.data)},
              {"title": "k= " + str(k[i//3])}],  # layout attribute
        label=str(k[i//3])
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][-1] = True
    steps.append(step)

sliders = [dict(
    active=10,
    currentvalue={"prefix": "Wave number: "},
    pad={"t": 50},
    steps=steps
)]

fig_k.update_layout(
    sliders=sliders
)

fig_k.update_layout(
    scene = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-1,1],),
        zaxis = dict(nticks=4, range=[-1,1],),
        xaxis_title='Phi',
        yaxis_title='Imaginary',
        zaxis_title='Real',))
    #width=1000,
    #margin=dict(r=20, l=10, b=10, t=10))
fig_k.update_layout(scene_aspectmode='manual',
                  scene_aspectratio=dict(x=2, y=1, z=1))

fig_k.show()
fig_k.write_html("wavefunction_changing_k.html")

# Changing sig plot
# Create figure
fig_sig = go.Figure()

# Add traces, one for each slider step
k = 3
sig_arr = np.logspace(-0.71, 0.5, 4)
phi_0 = 0
sig = 1
x=np.arange(-5, 5.05, 0.05)

for sig_i in sig_arr:
    psi = np.multiply(np.exp(1j*k*x), np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig_i**2))/(sig_i*np.sqrt(2*np.pi))))
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=x,
            y=np.imag(psi),
            z=np.real(psi),
            mode="lines"))
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=x,
            y=-2*np.ones(psi.shape),
            z=np.real(psi),
            mode="lines"))
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= x,
            y= np.imag(psi),
            z= -2*np.ones(psi.shape),
            mode="lines"))
    z2 = np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig_i**2))/(sig_i*np.sqrt(2*np.pi)))
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            x=x,
            y= -2*np.ones(x2.shape),
            z=z2,
            name="prob amplitude", mode="lines", line=dict(color='black', width=2)))

# Make 10th trace visible
fig_sig.data[4].visible = True
fig_sig.data[5].visible = True
fig_sig.data[6].visible = True
fig_sig.data[7].visible = True

# Create and add slider
steps = []
for i in range(0, len(fig_sig.data), 4):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig_sig.data)},
              {"title": "Sigma: " + str(round(sig_arr[i//4],2))}],  # layout attribute
        label=str(round(sig_arr[i//4],2))
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+3] = True
    steps.append(step)

sliders = [dict(
    active=1,
    currentvalue={"prefix": "Standard diviation: "},
    pad={"t": 40},
    steps=steps
)]

fig_sig.update_layout(
    sliders=sliders
)

fig_sig.update_layout(
    scene = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-2,2],),
        zaxis = dict(nticks=4, range=[-2,2],),
        xaxis_title='Phi',
        yaxis_title='Real',
        zaxis_title='Imaginary'))
    #width=1000,
    #margin=dict(r=20, l=10, b=10, t=10))
fig_sig.update_layout(scene_aspectmode='manual',
                  scene_aspectratio=dict(x=2, y=1, z=1))

fig_sig.show()
fig_sig.write_html("wavefunction_changing_sig.html")

# Changing sig plot
# Create figure
fig_mean = go.Figure()

# Add traces, one for each slider step
k = 3
sig = 1
phi_0 = np.arange(-3, 3.5, 0.5)
sig = 1
x=np.arange(-5, 5.05, 0.05)

for phi_0_i in phi_0:
    psi = np.multiply(np.exp(1j*k*x), np.sqrt(np.exp(-np.power(x-phi_0_i, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi))))
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=x,
            y=np.imag(psi),
            z=np.real(psi),
            mode="lines"))
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=x,
            y=-np.ones(psi.shape),
            z=np.real(psi),
            mode="lines"))
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= x,
            y= np.imag(psi),
            z= -np.ones(psi.shape),
            mode="lines"))
    z2 = np.sqrt(np.exp(-np.power(x-phi_0_i, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi)))
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            x=x,
            y= -np.ones(x2.shape),
            z=z2,
            name="prob amplitude", mode="lines", line=dict(color='black', width=2)))

# Make 10th trace visible
fig_mean.data[24].visible = True
fig_mean.data[25].visible = True
fig_mean.data[26].visible = True
fig_mean.data[27].visible = True

# Create and add slider
steps = []
for i in range(0, len(fig_mean.data), 4):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig_mean.data)},
              {"title": "Phi_0= " + str(phi_0[i//4])}],  # layout attribute
        label=str(phi_0[i//4])
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+3] = True
    steps.append(step)

sliders = [dict(
    active=6,
    currentvalue={"prefix": "Mean: "},
    pad={"t": 40},
    steps=steps
)]

fig_mean.update_layout(
    sliders=sliders
)

fig_mean.update_layout(
    scene = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-1,1],),
        zaxis = dict(nticks=4, range=[-1,1],),
        xaxis_title='Phi',
        yaxis_title='Real',
        zaxis_title='Imaginary'))
    #width=1000,
    #margin=dict(r=20, l=10, b=10, t=10))
fig_mean.update_layout(scene_aspectmode='manual',
                  scene_aspectratio=dict(x=2, y=1, z=1))

fig_mean.show()
fig_mean.write_html("wavefunction_changing_mu.html")


import numpy as np
from numpy.random import randn
import pandas as pd
import statsmodels.tsa.stattools as ts

class TimeSeriesBehavior:
    """
    This class provides functions to determine the behavior of a time series,
    specifically whether it is
        - A random walk. It has no memory. Examples: Brownian motion
        - A mean reverting series. Examples: Ornstein-Uhlenbeck process
        - A trending series
    """

    def __init__(self, time_series: pd.Series):
        self.time_series = time_series

    def augmented_dickey_fuller_test(self, max_lag: int = 1, pvalue_thresh: float = 0.05):
        """

        Null Hypothesis: The time series is not mean reverting

        :param max_lag:
        :param p_value_thresh:
        :return: True if time series is mean reverting, False if not
        """
        adfstat, pvalue, usedlag, nobs, critvalues = ts.adfuller(self.time_series, maxlag=max_lag)
        return pvalue < pvalue_thresh \
               and (adfstat < critvalues[0] and adfstat < critvalues[1] and adfstat < critvalues[2])

    def johansen_test(self, p_value_thresh: float = 0.95):
        pass

    def hurst_exponent(self, lag_range: tuple = (2, 100)):
        """
        Calculates the stationarity of the series. A time series (or stochastic process) is defined to be strongly
        stationary if its joint probability distribution is invariant under translations in time or space.
        In particular, and of key importance for traders, the mean and variance of the process do not change over time
        or space and they each do not follow a trend.

        A critical feature of stationary price series is that the prices within the series diffuse from their initial
        value at a rate slower than that of a Geometric Brownian Motion. By measuring the rate of this diffusive
        behaviour we can identify the nature of the time series.

        The Hurst Exponent tells you whether a series is
            * Geometric random walk (H=0.5)
            * Mean-reverting series (H<0.5)
            * Trending Series (H>0.5)

        Category: Measure of Autocorrelation

        :return:
        """

        lags = range(lag_range[0], lag_range[1])  # Create the range of lag values
        # Calculate the array of the variances of the lagged differences
        tau = [np.sqrt(np.std(np.subtract(self.time_series[lag:], self.time_series[:-lag]))) for lag in lags]
        poly = np.polyfit(np.log(lags), np.log(tau), 1)  # Use a linear fit to estimate the Hurst Exponent
        return poly[0] * 2.0  # Return the Hurst exponent from the polyfit output

if __name__ == '__main__':
    geometric_brownian_motion = np.log(np.cumsum(randn(100000)) + 1000)
    mean_reverting_series = np.log(randn(100000) + 1000)
    trending_series = np.log(np.cumsum(randn(100000) + 1) + 1000)

import numpy,from numpy,import statsmodels


# Source: http://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
# http://people.wku.edu/qi.li/teaching/446/cg14_curve_surface.pdf
import numpy as np
from utils import distance

def CatmullRomSpline(P0, P1, P2, P3, nPoints=100):
    """
    P0, P1, P2, and P3 should be (x,y) point pairs that define the Catmull-Rom spline.
    nPoints is the number of points to include in this curve segment.
    """
    # Convert the points to numpy so that we can do array multiplication
    P0, P1, P2, P3 = map(np.array, [P0, P1, P2, P3])

    # Calculate t0 to t4
    alpha = 0.5
    def tj(ti, Pi, Pj):
        xi, yi = Pi
        xj, yj = Pj
        return ( ( (xj-xi)**2 + (yj-yi)**2 )**0.5 )**alpha + ti

    t0 = P0[0]
    t1 = tj(t0, P0, P1)
    t2 = tj(t1, P1, P2)
    t3 = P3[0]

    # Only calculate points between P1 and P2
    t = np.linspace(t1,t2,nPoints)

    # Reshape so that we can multiply by the points P0 to P3
    # and get a point for each value of t.
    t = t.reshape(len(t),1)

    A1 = (t1-t)/(t1-t0)*P0 + (t-t0)/(t1-t0)*P1
    A2 = (t2-t)/(t2-t1)*P1 + (t-t1)/(t2-t1)*P2
    A3 = (t3-t)/(t3-t2)*P2 + (t-t2)/(t3-t2)*P3

    B1 = (t2-t)/(t2-t0)*A1 + (t-t0)/(t2-t0)*A2
    B2 = (t3-t)/(t3-t1)*A2 + (t-t1)/(t3-t1)*A3

    C  = (t2-t)/(t2-t1)*B1 + (t-t1)/(t2-t1)*B2
    return C

def CatmullRomLoop(loop, pointsPerUnitDist=1.):
    """
    Calculate Catmull Rom for a list of points, named loop, with loop[0] == loop[-1].
    """
    if len(loop) < 4:
        raise ValueError("Loop must have at least 4 points in it")
    ret = []
    # Add extra control points to ends
    loop = [loop[-2],] + loop + [loop[1],]
    # Produce coords for loop
    for i in xrange(len(loop)-3):
        numPoints = int(distance(loop[i+1], loop[i+2]) * pointsPerUnitDist)
        ret.append(CatmullRomSpline(loop[i], loop[i+1], loop[i+2], loop[i+3], nPoints=numPoints))
    ret = [tuple(coords) for seg in ret for coords in seg]
    return ret


from linear_regression import linear_regression_noreg,linear_regression_invertible, regularized_linear_regression, tune_lambda, mean_absolute_error,mapping_data
from data_loader import data_processing_linear_regression
import numpy as np
import pandas as pd

filename = 'winequality-white.csv'

print("\n======== Question 1.1 and Question 1.2 ========")
Xtrain, ytrain, Xval, yval, Xtest, ytest = data_processing_linear_regression(filename, False, False, 0)
w = linear_regression_noreg(Xtrain, ytrain)
print("dimensionality of the model parameter is ", w.shape, ".", sep="")
print("model parameter is ", np.array_str(w))
mae = mean_absolute_error(w, Xtrain, ytrain)
print("MAE on train is %.5f" % mae)
mae = mean_absolute_error(w, Xval, yval)
print("MAE on val is %.5f" % mae)
mae = mean_absolute_error(w, Xtest, ytest)
print("MAE on test is %.5f" % mae)

print("\n======== Question 1.3 ========")
Xtrain, ytrain, Xval, yval, Xtest, ytest = data_processing_linear_regression(filename, True, False, 0)
w = linear_regression_invertible(Xtrain, ytrain)
print("dimensionality of the model parameter is ", w.shape, ".", sep="")
print("model parameter is ", np.array_str(w))
mae = mean_absolute_error(w, Xtrain, ytrain)
print("MAE on train is %.5f" % mae)
mae = mean_absolute_error(w, Xval, yval)
print("MAE on val is %.5f" % mae)
mae = mean_absolute_error(w, Xtest, ytest)
print("MAE on test is %.5f" % mae)

print("\n======== Question 1.4 ========")
Xtrain, ytrain, Xval, yval, Xtest, ytest = data_processing_linear_regression(filename, True, False, 0)
w = regularized_linear_regression(Xtrain, ytrain, 0.1)
print("dimensionality of the model parameter is ", w.shape, ".", sep="")
print("model parameter is ", np.array_str(w))
mae = mean_absolute_error(w, Xtrain, ytrain)
print("MAE on train is %.5f" % mae)
mae = mean_absolute_error(w, Xval, yval)
print("MAE on val is %.5f" % mae)
mae = mean_absolute_error(w, Xtest, ytest)
print("MAE on test is %.5f" % mae)

print("\n======== Question 1.5========")
Xtrain, ytrain, Xval, yval, Xtest, ytest = data_processing_linear_regression(filename, False, False, 0)
bestlambd = tune_lambda(Xtrain, ytrain, Xval, yval)
print("Best Lambda =  " + str(bestlambd))
w = regularized_linear_regression(Xtrain, ytrain, bestlambd)
print("dimensionality of the model parameter is ", len(w), ".", sep="")
print("model parameter is ", np.array_str(w))
mae = mean_absolute_error(w, Xtrain, ytrain)
print("MAE on train is %.5f" % mae)
mae = mean_absolute_error(w, Xval, yval)
print("MAE on val is %.5f" % mae)
mae = mean_absolute_error(w, Xtest, ytest)
print("MAE on test is %.5f" % mae)

print("\n======== Question 1.6.1 (power = 2) ========")
print("if your maaping function is correct, simplely change the 'power' value to see how MAE change when 'power' changes")
power = 2
Xtrain, ytrain, Xval, yval, Xtest, ytest = data_processing_linear_regression(filename, False, True, power)
bestlambd = tune_lambda(Xtrain, ytrain, Xval, yval)
print("Best Lambda =  ", bestlambd, sep="")
w = regularized_linear_regression(Xtrain, ytrain, bestlambd)
print("dimensionality of the model parameter is ", len(w), ".", sep="")
print("model parameter is ", np.array_str(w))
mae = mean_absolute_error(w, Xtrain, ytrain)
print("MAE on train is %.5f" % mae)
mae = mean_absolute_error(w, Xval, yval)
print("MAE on val is %.5f" % mae)
mae = mean_absolute_error(w, Xtest, ytest)
print("MAE on test is %.5f" % mae)

print("\n======== Question 1.6.2 (power = higher)========")
print("if your maaping function is correct, simplely change the 'power' value to see how MSE change when 'power' changes")
power = 20
for i in range(2, power):
    Xtrain, ytrain, Xval, yval, Xtest, ytest = data_processing_linear_regression(filename, False, True, i)
    bestlambd = tune_lambda(Xtrain, ytrain, Xval, yval)
    print('best lambd is ' + str(bestlambd))
    w = regularized_linear_regression(Xtrain, ytrain, bestlambd)
    mae = mean_absolute_error(w, Xtrain, ytrain)
    print('when power = ' + str(i))
    print("MAE on train is %.5f" % mae)
    mae = mean_absolute_error(w, Xval, yval)
    print("MAE on val is %.5f" % mae)
    mae = mean_absolute_error(w, Xtest, ytest)
    print("MAE on test is %.5f" % mae)


import matplotlib.pyplot as plt
import numpy

x = numpy.arange(-1.0, 1.0, 0.001)

y = numpy.zeros( (len(x),) )
for i in range(len(x)):
    pos = x[i]
    if pos < 0.0:
        y[i] = -2*pos - 1
    else:
        y[i] = -pow(1+5*numpy.square(pos),-0.5)-numpy.square(pos)*pow(1 + 5*numpy.square(pos),-1.5)-pow(pos,4)/16.0

fig_potential = plt.figure(1,figsize=(6,6))
plt.plot(x,-0.05*0.001*numpy.cumsum(y))
plt.ylabel('Potential $G(x)$ / a.u.')
plt.xlabel('Position $x$ / a.u.')

fig_potential.subplots_adjust(left=0.15, bottom=0.1, right=0.95, top=0.95)

fig_potential.savefig('fig_potential.pdf')

fig_force = plt.figure(2,figsize=(6,6))
plt.plot(x,0.05*y)
plt.ylabel('Force $F_g(x)$ / a.u.')
plt.xlabel('Position $x$ / a.u.')

fig_force.subplots_adjust(left=0.15, bottom=0.1, right=0.95, top=0.95)

fig_force.savefig('fig_force.pdf')

plt.show()

#ifelse(T.lt(pos,0.0),-2*pos - 1,-T.pow(1+5*T.numpy.square(pos),-0.5)-T.numpy.square(pos)*T.pow(1 + 5*T.numpy.square(pos),-1.5)-T.pow(pos,4)/16.0) - 0.25*vt


import math
import matplotlib.pyplot as plt
from .GeneralDistribution import Distribution
import numpy as np

class Exponential(Distribution):
    """
    Exponential distribution class for calculating and visualizing a Exponential distribution.

    Attributes:
        mean (float) representing the mean value of the distribution
        stdev (float) representing the standard deviation of the distribution
        data_list (list of floats) a list of floats to be extracted from the data file
        rate (float) rate at which events occur
    """

    def __init__(self, rate = 1):

        self.rate = rate
        Distribution.__init__(self, self.calculate_mean(), self.calculate_stdev())

        self.data = np.random.exponential(1.0/self.rate, 500)

    def calculate_mean(self):

        """
        Function to calculate the mean from rate

        Args:
            None

        Returns:
            float: mean of the data set

        """

        self.mean = 1.0/self.rate

        return self.mean

    def calculate_stdev(self):

        """
        Function to calculate the standard deviation from rate

        Args:
            None

        Returns:
            float: standard deviation of the data set

        """

        self.stdev = 1.0/self.rate

        return self.stdev

    def read_data_file(self, file_name):

        """
		Function to read in data from a txt file. The txt file should have
		one number (float) per line. The numbers are stored in the data attribute.
		After reading in the file, the mean and standard deviation are calculated

		Args:
			file_name (string): name of a file to read from

		Returns:
			float: rate parameter

        """
        Distribution.read_data_file(self, file_name)

        self.rate = 1.0 * len(self.data)/sum(self.data)

        self.mean = self.calculate_mean()
        self.stdev = self.calculate_stdev()

        return self.rate

    def pdf(self, k):

        """
        Probability density function calculator for the poisson distribution.

        Args:
            k (float): point for calculating the probability density function

        Returns:
            float: probability density function output
        """

        return self.rate * math.exp(-self.rate * k) if k >= 0 else 0

    def cdf(self, k):

        """
        Cumulative density function calculator for the poisson distribution.

        Args:
            k (float): point for calculating the probability density function

        Returns:
            float: cumulative density function output
        """

        return 1 - math.exp(-self.rate * k) if k >= 0 else 0

    def plot(self):

        """
        Function to plot the data, pdf, cdf of the exponential distribution

        Args:
            None

        Returns:
            list: x values for the both plot
            list: y1 values for the pdf plot
            listL y2 values for the cdf plot

        """

        mu = self.mean
        sigma = self.stdev
        data = self.data.sort()

        x = list(set(self.data))
        x.sort()
        y1 = []
        y2 = []

		# calculate the y values to visualize
        for i in x:
            y1.append(self.pdf(i))
            y2.append(self.cdf(i))

		# make the plot
        fig, axes = plt.subplots(3,sharex=True)
        fig.subplots_adjust(hspace=.5)

        axes[0].hist(self.data, len(x), density=True)
        axes[0].set_title('Normed Histogram of Data')
        axes[0].set_ylabel('Density')

        axes[1].plot(x, y1)
        axes[1].set_title('Probability Distribution for Data')
        axes[1].set_ylabel('Probability Density')

        axes[2].plot(x, y2)
        axes[2].set_title('Cumulative Distribution for Data')
        axes[2].set_ylabel('Probability')

        plt.show()

        return x, y1, y2

    def __add__(self, other):
        pass

    def __repr__(self):

        """
        Function to output the characteristics of the Exponential instance

        Args:
            None

        Returns:
            string: characteristics of the Exponential

        """

        return "mean {}, standard deviation {}, rate parameter {}".\
        format(self.mean, self.stdev, self.rate)


import numpy as np

def get_mu(a, b):
    """Compute the mode of the latitude distribution."""
    alpha = np.exp(a * 5)
    beta = np.exp(np.log(0.5) + b * (5 - np.log(0.5)))
    term = (
        4 * alpha ** 2
        - 8 * alpha
        - 6 * beta
        + 4 * alpha * beta
        + beta ** 2
        + 5
    )
    return 2 * np.arctan(np.sqrt(2 * alpha + beta - 2 - np.sqrt(term)))

def get_sigma(a, b):
    """Compute the local standard deviation of the latitude distribution."""
    alpha = np.exp(a * 5)
    beta = np.exp(np.log(0.5) + b * (5 - np.log(0.5)))
    mu = get_mu(a, b)
    term = (
        1
        - alpha
        + beta
        + (beta - 1) * np.cos(mu)
        + (alpha - 1) / np.cos(mu) ** 2
    )
    return np.sin(mu) / np.sqrt(term)

def get_J(a, b):
    """
    Compute the Jacobian for the transformation (a, b) <--> (mu, sigma).

    """
    alpha = np.exp(a * 5)
    beta = np.exp(np.log(0.5) + b * (5 - np.log(0.5)))
    mu = get_mu(a, b)
    sigma = get_sigma(a, b)
    C = 5 * (5 - np.log(0.5))
    return (
        C
        * (alpha * beta * (1 + np.cos(mu)) ** 3 * np.sin(2 * mu) ** 3)
        / (
            sigma
            * (-3 + 2 * alpha + beta + (-1 + 2 * alpha + beta) * np.cos(mu))
            * (
                2 * (-1 + alpha + beta)
                + 3 * (-1 + beta) * np.cos(mu)
                - 2 * (-1 + alpha - beta) * np.cos(2 * mu)
                + (-1 + beta) * np.cos(3 * mu)
            )
            ** 2
        )
    )

def test_jacobian(ntests=100, eps=1e-6):
    """
    Numerically show that our expression for the Jacobian agrees
    with its definition in terms of derivatives.

    """
    np.random.seed(0)

    for n in range(ntests):
        a = np.random.random()
        b = np.random.random()

        # Analytic expression
        J = get_J(a, b)

        # Numerical expression
        dmuda = (get_mu(a + eps, b) - get_mu(a - eps, b)) / (2 * eps)
        dmudb = (get_mu(a, b + eps) - get_mu(a, b - eps)) / (2 * eps)
        dsigmada = (get_sigma(a + eps, b) - get_sigma(a - eps, b)) / (2 * eps)
        dsigmadb = (get_sigma(a, b + eps) - get_sigma(a, b - eps)) / (2 * eps)
        J_num = dmuda * dsigmadb - dmudb * dsigmada

        assert np.allclose(J, J_num)


from numpy import array, dot, diag, zeros, ones

# Here are the important functions
def Setup_PDE_Alpha_ConstantBC(u0, x, dt, nr_times, t0):
	""" This function solves the heat equation :
			u_t = u_xx
	by using the Euler method. The function takes
	an initial condition u0, a domain  x,
	a time step dt, the number of times to run the loop,
	and the initial time, t0, as input.
	"""
	# Important Constants, etc.
	time = t0
	dx = x[1] - x[0]
	numUnknowns = len(u0) - 2
	r = dt/(dx**2) # Exponents are done with a double asterisk in python
	mainDiagonal = -2*ones(numUnknowns)
	offDiagonal = ones(numUnknowns - 1)
	T = MakeTridiagonalMatrix(mainDiagonal, offDiagonal) # We could consider a better place to make T to make the code faster.
	u = u0

	# Make the boundary condition vector
	BoundaryConditions = zeros(numUnknowns)
	BoundaryConditions[0] = u0[0]
	BoundaryConditions[-1] = u0[-1]

	# Loop to perform the calculations
	for step in range(nr_times):
		u[1:-1] = u[1:-1] + r*dot(T, u[1:-1]) + r*BoundaryConditions
		time = time + dt # We could reconsider how to do this to minimze rounding errors.

	# Return what we want
	return u, time

def MakeTridiagonalMatrix(main, offset_one):
	"""This function will make a tridiagonal 2D array (NOT a matrix)
	which has the main array on its main diagonal and the offset_one
	array on its super and sub diagonals.
	"""
	return diag(main) + diag(offset_one, k = -1) + diag(offset_one, k = 1)


from __future__ import division, print_function
import numpy as np

# Generates the Gauss-Legendre knots and weights for an arbitrary integration interval (a..b)
def leggauss_ab(n=96, a=-1.0, b=1.0):
  assert(n>0)
  from numpy.polynomial.legendre import leggauss
  x,w = leggauss(n)
  x = (b-a) * 0.5 * x+(b+a) * 0.5
  w = w * (b-a) * 0.5
  return x,w

# Generates the Gauss-Legendre knots and weights
#   Fortran version is written by James Talman
def gauleg_ab(n=96, a=-1.0, b=1.0, eps=1.0e-15, cmx=15):
  assert(n>0)
  x,w = np.zeros((n)),np.zeros((n))

  m=(n+1)//2
  for i in range(m):
    z=np.cos(np.pi*(i+1-0.25)/(n+0.5))
    for c in range(cmx):
      p1,p2=1.0,0.0
      for j in range(1,n+1):
        p3=p2
        p2=p1
        p1=((2.0*j-1)*z*p2-(j-1)*p3)/j
      pp=n*(z*p1-p2)/(z*z-1.0)
      z1=z
      z=z1-p1/pp
      if abs(z-z1)<eps : exit

    x[ i],x[-i-1] = -z,z
    w[ i]=w[-i-1] = 2.0/((1.0-z*z)*pp*pp)

  x = (b-a) * 0.5 * x+(b+a) * 0.5
  w = w * (b-a) * 0.5
  return x,w

if __name__ == '__main__':
  from pyscf.nao.m_gauleg import gauleg_ab
  from numpy.polynomial.legendre import leggauss
  for n in range(1,320):
    xx1,ww1=leggauss(n)
    xx2,ww2=gauleg_ab(n)
    if np.allclose(xx1,xx2) and np.allclose(ww1,ww2):
      print(n, 'allclose')
    else:
      print(n, (xx1-xx2).sum(), (ww1-ww2).sum())


import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import Normalize
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401 unused import

def plot_function_surface(x1, x2, Z, C, t, cmap='gist_earth'):
    fig = plt.figure(figsize=(18, 12))
    ax = fig.add_subplot(projection='3d')
    vmax = np.max(np.abs(np.ravel(C)))
    vmin = -vmax
    norm = Normalize(vmin, vmax)
    scamap = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
    ax.plot_surface(x1, x2, Z, facecolors=scamap.to_rgba(C), cmap=cmap)
    ax.set_xlabel('x1')
    ax.set_ylabel('x2')
    ax.set_zlabel('z')
    ax.set_title(t)
    fig.colorbar(scamap, shrink=0.5, aspect=10)
    plt.tight_layout()

def f(x1, x2):
    # return -10 * x1 + 0 * x2 + 100
    return x2 * np.sin(x1 ** 2)
    # return (x1**2) * x2

def df_x1(x1, x2):
    # return np.ones_like(x1) * -10
    return 2 * x1 * x2 * np.cos(x1 ** 2)
    # return 2 * x1 * x2

def df_x2(x1, x2):
    # return np.zeros_like(x2)
    return np.sin(x1 ** 2)
    # return x1 ** 2

def main():
    x1 = np.linspace(-2, 2, 50)
    x2 = np.linspace(-5, 5, 50)
    x1, x2 = np.meshgrid(x1, x2)

    # Function plot
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=f(x1, x2), t='Z=f(x1, x2), C=f(x1, x2)')

    # Function plots with x1 and x2 gradients
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=df_x1(x1, x2), t='Z=f(x1, x2), C=df_x1(x1, x2)')
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=df_x2(x1, x2), t='Z=f(x1, x2), C=df_x2(x1, x2)')

    # Function plots with x1 gradient * input and x2 gradient * input
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=df_x1(x1, x2) * x1, t='Z=f(x1, x2), C=df_x1(x1, x2) * x1')
    # plot_function_surface(x1=x1, x2=x2, Z=f(x1, x2), C=df_x2(x1, x2) * x2, t='Z=f(x1, x2), C=df_x2(x1, x2) * x2')

    # Gradient plots
    plot_function_surface(x1=x1, x2=x2, Z=df_x1(x1, x2), C=df_x1(x1, x2), t='Z=df_x1(x1, x2), C=df_x1(x1, x2)')
    # plot_function_surface(x1=x1, x2=x2, Z=df_x2(x1, x2), C=df_x2(x1, x2), t='Z=df_x2(x1, x2), C=df_x2(x1, x2)')

    # Gradient * input plots
    # plot_function_surface(x1=x1, x2=x2, Z=df_x1(x1, x2) * x1, C=df_x1(x1, x2) * x1, t='Z=df_x1(x1, x2) * x1, C=df_x1(x1, x2) * x1')
    # plot_function_surface(x1=x1, x2=x2, Z=df_x2(x1, x2) * x2, C=df_x2(x1, x2) * x2, t='Z=df_x2(x1, x2) * x2, C=df_x2(x1, x2) * x2')

    plt.show()

main()


###########################################################################
# This file just takes a derivative of the data
############################################################################

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm

def checkStep2( x, y, z, plot=False, plotTitle = 'u_y over u_x' ):
    # Step 2 says that if u_y(x,y,0) = \beta u_x(x,y,0) for some \beta, then the
    # data is one-dimensional. We calculate u_y and u_x then divide pointwise to
    # see if there is a pattern.
    u_x, u_y = np.gradient( z );
    u_y_over_u_x = np.divide( u_y, u_x );

    if plot==True:
        fig = plt.figure()
        ax1 = plt.gca( projection = '3d' )
        surf = ax1.plot_surface(x,y,u_y_over_u_x, cmap = cm.coolwarm);
        ax1.set_xlabel('x'); ax1.set_ylabel('y');
        fig.suptitle(plotTitle)

        #fig = plt.figure()
        #ax2 = plt.gca( projection = '3d' )
        #surf = ax2.plot_surface(x2,y2,u_y_over_u_x2, cmap = cm.coolwarm);
        #ax2.set_xlabel('x'); ax1.set_ylabel('y');
        #fig.suptitle(plotTitle + ' edges removed')

    # Find average, throwing out outliers which are very large numbers
    # First Remove edge cases
    u_y_over_u_x2 = u_y_over_u_x[1:-1, 1:-1]
    u_y_over_u_x2 = u_y_over_u_x2[ np.isfinite(u_y_over_u_x2) ]
    print('The mean of u_y/u_x = ' + str(u_y_over_u_x2.mean()) +
            ' with standard deviation = ' + str(u_y_over_u_x2.std()) )

    return u_y_over_u_x


import numpy as np

class LSE(object):
    """
    Least Squares Error Linear Regression.
    # Attributes
    ----------
    coef_ : array, shape (1, )
        Estimated coefficient for the linear regression problem.
    intercept_ : float
        Independent term in the linear model.

    # Examples
    --------
    >>> import numpy as np
    >>> from mlitera.linreg import LSE
    >>> X = np.array([[1], [2], [3], [4], [5]])
    >>> # y = 2x + 3
    >>> y = (2 * X + 3).flatten()
    >>> m = LSE().fit(X, y)
    >>> m.score(y, m.predict(X))
    1.0
    >>> m.intercept_
    3.0
    >>> m.coef_
    2.0
    """

    def fit(self, X, y):
        """
        Fit linear model.
        Parameters
        ----------
        X : array-like, shape (n_samples, 1)
            Training data
        y : array_like, shape (n_samples, )
            Target values.
        Returns
        -------
        self : returns an instance of self.
        """
        X = X.flatten() # unrolled X
        N = len(X)
        theta1 = ( sum(X * y) - (N * X.mean() * y.mean()) ) / sum((X - X.mean()) ** 2)
        theta0 = y.mean() - theta1 * X.mean()
        self.thetas = np.array([theta0, theta1])
        return self

    @property
    def intercept_(self):
        """ Independent term in the linear model."""
        return self.thetas[0]

    @property
    def coef_(self):
        """ Estimated coefficient for the linear regression problem."""
        return self.thetas[-1]

    def score(self, y_true, y_pred):
        """Returns the coefficient of determination R^2 of the prediction.
        The coefficient R^2 is defined as (1 - SSR/SST), where SSR is the residual
        sum of squares sum((y_true - y_pred) ** 2) and SST is the total
        sum of squares sum((y_true - y_true.mean()) ** 2).
        The best possible score is 1.0 and it can be negative (because the
        model can be arbitrarily worse). A constant model that always
        predicts the expected value of y, disregarding the input features,
        would get a R^2 score of 0.0.
        Parameters
        ----------
        y_true : array-like, shape = (n_samples)
            True values for X.
        y_pred : array-like, shape = (n_samples)
            Prediction values for X.
        Returns
        -------
        score : float
            R^2
        """
        SST = sum((y_true - y_true.mean()) ** 2)
        SSR = sum((y_true - y_pred) ** 2)
        return 1 - SSR/ SST

    def predict(self, X):
        """Predict using the linear model
        Parameters
        ----------
        X : array_like , shape (n_samples, 1)
            Samples.
        Returns
        -------
        y : array, shape (n_samples,)
            Returns predicted values.
        """
        X = X.flatten() # unrolled X
        y = self.intercept_ + self.coef_ * X
        return y

if __name__ == '__main__':
    import sys
    import doctest
    sys.path.append("/home/jose/machine-learning-itera")
    doctest.testmod()


# Utitlity file with functions for handling rotations
#
# Author: Trevor Ablett
# University of Toronto Institute for Aerospace Studies

import numpy as np

def skew_symmetric(v):
    """ Skew symmetric operator for a 3x1 vector. """
    return np.array(
        [[0, -v[2], v[1]],
         [v[2], 0, -v[0]],
         [-v[1], v[0], 0]]
    )

class Quaternion():
    def __init__(self, w=1., x=0., y=0., z=0., axis_angle=None, euler=None):
        """
        Allow initialization either with explicit wxyz, axis angle, or euler XYZ (RPY) angles.

        :param w: w (real) of a quaternion.
        :param x: x (i) of a quaternion.
        :param y: y (j) of a quaternion.
        :param z: z (k) of a quaternion.
        :param axis_angle: Set of three values from axis-angle representation, as list or [3,] or [3,1] np.ndarray.
                           See C2M5L2 Slide 7 for details.
        :param euler: Set of three angles from euler XYZ rotating frame angles.
        """
        if axis_angle is None and euler is None:
            self.w = w
            self.x = x
            self.y = y
            self.z = z
        elif euler is not None and axis_angle is not None:
            raise AttributeError("Only one of axis_angle and euler can be specified.")
        elif axis_angle is not None:
            if not (type(axis_angle) == list or type(axis_angle) == np.ndarray) or len(axis_angle) != 3:
                raise ValueError("axis_angle must be list or np.ndarray with length 3.")
            axis_angle = np.array(axis_angle)
            norm = np.linalg.norm(axis_angle)
            self.w = np.cos(norm / 2)
            if norm < 1e-50:  # to avoid instabilities and nans
                self.x = 0
                self.y = 0
                self.z = 0
            else:
                imag = axis_angle / norm * np.sin(norm / 2)
                self.x = imag[0].item()
                self.y = imag[1].item()
                self.z = imag[2].item()
        else:
            roll = euler[0]
            pitch = euler[1]
            yaw = euler[2]

            cy = np.cos(yaw * 0.5)
            sy = np.sin(yaw * 0.5)
            cr = np.cos(roll * 0.5)
            sr = np.sin(roll * 0.5)
            cp = np.cos(pitch * 0.5)
            sp = np.sin(pitch * 0.5)

            # static frame
            self.w = cr * cp * cy + sr * sp * sy
            self.x = sr * cp * cy - cr * sp * sy
            self.y = cr * sp * cy + sr * cp * sy
            self.z = cr * cp * sy - sr * sp * cy

            # rotating frame
            # self.w = cr * cp * cy - sr * sp * sy
            # self.x = cr * sp * sy + sr * cp * cy
            # self.y = cr * sp * cy - sr * cp * sy
            # self.z = cr * cp * sy + sr * sp * cy

    def __repr__(self):
        return "Quaternion (wxyz): [%2.5f, %2.5f, %2.5f, %2.5f]" % (self.w, self.x, self.y, self.z)

    def to_mat(self):
        v = np.array([self.x, self.y, self.z]).reshape(3,1)
        return (self.w ** 2 - np.dot(v.T, v)) * np.eye(3) + \
               2 * np.dot(v, v.T) + 2 * self.w * skew_symmetric(v)

    def to_euler(self):
        """ Return as xyz (roll pitch yaw) Euler angles, with a static frame """
        roll = np.arctan2(2 * (self.w * self.x + self.y * self.z), 1 - 2 * (self.x**2 + self.y**2))
        pitch = np.arcsin(2 * (self.w * self.y - self.z * self.x))
        yaw = np.arctan2(2 * (self.w * self.z + self.x * self.y), 1 - 2 * (self.y**2 + self.z**2))
        return np.array([roll, pitch, yaw])

    def to_numpy(self):
        """ Return numpy wxyz representation. """
        return np.array([self.w, self.x, self.y, self.z])

    def normalize(self):
        """ Return a normalized version of this quaternion to ensure that it is valid. """
        norm = np.linalg.norm([self.w, self.x, self.y, self.z])
        return Quaternion(self.w / norm, self.x / norm, self.y / norm, self.z / norm)

    def quat_mult(self, q, out='np'):
        """
        Give output of multiplying this quaternion by another quaternion.
        See equation from C2M5L2 Slide 7 for details.

        :param q: The quaternion to multiply by, either a Quaternion or 4x1 ndarray.
        :param out: Output type, either np or Quaternion.
        :return: Returns quaternion of desired type
        """

        v = np.array([self.x, self.y, self.z]).reshape(3, 1)
        sum_term = np.zeros([4,4])
        sum_term[0,1:] = -v[:,0]
        sum_term[1:, 0] = v[:,0]
        sum_term[1:, 1:] = -skew_symmetric(v)
        sigma = self.w * np.eye(4) + sum_term

        if type(q).__name__ == "Quaternion":
            quat_np = np.dot(sigma, q.to_numpy())
        else:
            quat_np = np.dot(sigma, q)

        if out == 'np':
            return quat_np
        elif out == 'Quaternion':
            quat_obj = Quaternion(quat_np[0], quat_np[1], quat_np[2], quat_np[3])
            return quat_obj

Course 2 - State Estimation and Localization/Course2FinalProject/rotations.py


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

# In[1]:

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

import statsmodels

from scipy import stats
from statsmodels.stats import stattools
from statsmodels.stats.weightstats import DescrStatsW

# <b>Datasets : </b> https://www.kaggle.com/kandij/mall-customers

# In[2]:

mall_data = pd.read_csv('datasets/mall_data_processed.csv', index_col=0)

mall_data.head(5)

# In[3]:

mall_data.shape

# In[4]:

mall_data.describe()

# In[5]:

income_descr = DescrStatsW(mall_data['annual_income'])

age_descr = DescrStatsW(mall_data['age'])

# In[6]:

q1_income = income_descr.quantile(0.25)

q3_income = income_descr.quantile(0.75)

# In[7]:

q1_income

# In[8]:

q3_income

# In[9]:

type(q1_income)

# In[10]:

iqr_income = q3_income.loc[0.75] - q1_income.loc[0.25]

iqr_income

# In[11]:

stats.iqr(mall_data['annual_income'])

# In[12]:

stats.iqr(mall_data['annual_income'], interpolation='lower')

# In[13]:

stats.iqr(mall_data['annual_income'], interpolation='higher')

# In[14]:

stats.iqr(mall_data['annual_income'], interpolation='midpoint')

# In[15]:

q1_income_np = np.percentile(mall_data['annual_income'], 25)

q1_income_np

# In[16]:

q3_income_np = np.percentile(mall_data['annual_income'], 75)

q3_income_np

# In[17]:

plt.figure(figsize=(12, 8))

sns.boxplot(mall_data['annual_income'], orient='v')

# In[18]:

plt.figure(figsize=(12, 8))

sns.boxplot(mall_data['spending_score'], orient='v')

# In[19]:

mall_data.head()

# In[20]:

plt.figure(figsize=(12, 8))

sns.boxplot(x='gender', y='annual_income', hue='gender', data=mall_data, orient='v')

# In[21]:

plt.figure(figsize=(12, 8))

sns.boxplot(x='gender', y='spending_score', hue='gender', data=mall_data, orient='v')

# In[22]:

plt.figure(figsize=(12, 8))

sns.boxplot(x='above_average_income', y='spending_score', hue='above_average_income',
            data=mall_data, orient='v')

# In[23]:

income_descr.var

# In[24]:

age_descr.var

# ### Calculating Standard Deviation

# In[25]:

income_descr.std

# In[26]:

age_descr.std

# #### Describe using stats

# In[27]:

stats.describe(mall_data['annual_income'])

# In[28]:

stats.describe(mall_data['age'])

# In[29]:

listOfSeries = [pd.Series(['Male', 20, 250000, 98, True], index=mall_data.columns ),
                pd.Series(['Female', 18, 280000, 20, True], index=mall_data.columns ),
                pd.Series(['Male', 78, 20000, 22, True], index=mall_data.columns )
               ]

# In[30]:

mall_updated = mall_data.append(listOfSeries , ignore_index=True)

mall_updated.tail()

# In[31]:

np.ptp(mall_data['annual_income'])

# In[32]:

np.ptp(mall_updated['annual_income'])

# In[33]:

stats.iqr(mall_data['annual_income'], interpolation='midpoint')

# In[34]:

stats.iqr(mall_updated['annual_income'], interpolation='midpoint')

# In[35]:

plt.figure(figsize=(12, 8))

sns.boxplot(mall_updated['spending_score'], orient='v')

# In[36]:

plt.figure(figsize=(12, 8))

sns.boxplot(mall_updated['annual_income'], orient='v')

# In[ ]:

import numpy,from scipy,import statsmodels,from statsmodels


import numpy as np
import numpy.linalg as la
import numpy.random as random
import math

class GMM:

    def fit(self, X, num_clusters=2, num_epochs=50):

        # describes the training criteria for the GMM using EM formulation
        num_samples = X.shape[0];
        num_dims = X.shape[1];

        """Step1 : Initialize the gaussian parameters and the discrete distribution"""
        pi_distribution = random.rand(num_clusters,);
        pi_distribution = pi_distribution / la.norm(pi_distribution, ord=1);

        print pi_distribution;

        mean = random.randn(num_clusters, num_dims);
        covariance = random.randn(num_clusters, num_dims, num_dims);

        phi = np.zeros(shape=(num_samples, num_clusters));

        prev_error = 0.0;
        new_error = 0.0;

        epsilon = 0.0001;

        for epoch_idx in range(num_epochs):

            """Step2: Assign the data to the clusters"""
            for sample_idx in range(num_samples):

                for cluster_idx in range(num_clusters):
                    phi[sample_idx, cluster_idx] = pi_distribution[cluster_idx] * \
                                                    self.compute_gaussian(np.reshape(X[sample_idx],(1,2)),
                                                                          np.reshape(mean[cluster_idx],(1,2)),
                                                                          covariance[cluster_idx]);

                # normalize the distribution over the weights
                phi[sample_idx] = phi[sample_idx] / la.norm(phi[sample_idx], ord=1);

            """Step3: Update the parameters of the model """
            # update the pi distribution
            nk = np.sum(phi, axis=0);
            pi_distribution = nk / num_samples;

            # update the mean of the clusters
            mean = np.matmul(np.transpose(phi), X);
            for cluster_idx in range(num_clusters):
                mean[cluster_idx] = mean[cluster_idx] / nk[cluster_idx];

            # update the covariance of the clusters
            for cluster_idx in range(num_clusters):

                sample = np.zeros(shape=(num_dims, num_dims));
                for sample_idx in range(num_samples):

                    tmp = X[sample_idx] - mean[cluster_idx];
                    tmp = np.reshape(tmp, (1,2));

                    sample = sample + phi[sample_idx,cluster_idx] * np.matmul(np.transpose(tmp), tmp);

                covariance[cluster_idx] = sample / nk[cluster_idx];

            # compute the error at the end of each iteration
            error = 0;
            for sample_idx in range(num_samples):

                sample_error = 0.0;
                for cluster_idx in range(num_clusters):
                    sample_error += (pi_distribution[cluster_idx] *
                              self.compute_gaussian(np.reshape(X[sample_idx],(1,2)),
                                                    np.reshape(mean[cluster_idx],(1,2)),
                                                    covariance[cluster_idx]));

                error += math.log(sample_error);

            prev_error = new_error;
            new_error = error;

            print ('Log likelihood:%0.5f at Epoch:%d' % (error, epoch_idx));
            print pi_distribution;
            if abs(new_error - prev_error) < epsilon:
                break;

        # return the theta,mean and the covariance
        return (phi, pi_distribution, mean, covariance);

    def compute_gaussian(self, x, mean, covariance):

        # computes the rbf distance of x using the mean and the covariance
        cov_inv = la.inv(covariance);
        tmp = np.matmul(x - mean , cov_inv);
        dist = np.exp(-np.matmul(tmp, np.transpose(x - mean)))/(math.sqrt(2 * np.pi) * np.abs(la.det(covariance)));

        return dist;


'''
module docstring for carrier equilibrium density
'''

from numpy import exp,sqrt,pi
from semic.constants.constants import value
from semic.math.fdint import fdint_approx

def find_n0(n_c=0,conduction_band_energy=0,fermi_energy=0,temp=0):
    '''
    Function to find nondegenerate Equilibrium electron density
    in the conduction band.

    n_c: Thermal effective density of states in the
        conduction band.

    conduction_band_energy: Conduction band edge energy in a semiconductor.
        This is the potential energy of electrons, including
        electrostatic potential.

    fermi_energy: Fermi energy, or the chemical potential for electrons.

    k_b: Boltzmann's constant in eV/K

    temp: Temperature in Kelvin

    n0 = n_c*exp(-(conduction_band_energy-fermi_energy)/(k_b*temp))
    '''
    kb_t = value('Boltzmann constant in eV/K') * temp
    n_0 = n_c*exp(-(conduction_band_energy-fermi_energy)/kb_t)

    return n_0

def find_nc(mass_c=0,temp=0):
    '''
    Function to find thermal effective density of states in
    conduction band.

    mass_c: The effective mass of a carrier in the conduction band

    k_b: Boltzmann constant in eV/K

    temp: Temperature in Kelvin

    hbar: reduced Planck constant in eV s

    n_c = 2*[mass_c*k_b*temp/(2pi*hbar^2)]^(3/2)
    '''
    kb_t = value('Boltzmann constant in eV/K') * temp
    h_bar = value('reduced Planck constant in eV s')
    n_c = 2*((mass_c*kb_t/(2*pi*(h_bar**2)))**(3/2))

    return n_c

def find_p0(n_v=0,valence_band_energy=0,fermi_energy=0,temp=0):
    '''
    Function to find the nondegenerate Equilibrium hole density
    in the valence band.

    n_v: Thermal effective density of states in valence band

    valence_band_energy: Valence band edge energy in a semiconductor.
        This is the potential energy of electrons, including
        electrostatic potential.

    k_b: Boltzmann constant in eV/K

    temp: Temperature in Kelvin

    p0 = n_v*exp(valence_band_energy-fermi_energy/k_b*temp)
    '''

    kb_t = value('Boltzmann constant in eV/K') * temp
    p_0 = n_v*exp((valence_band_energy-fermi_energy)/kb_t)

    return p_0

def find_nv(mass_v=0,temp=0):
    '''
    Function to find the thermal effective density
    of states in valence band.

    mass_v: The effective mass of of carrier in valence band

    temp: Temperature in Kelvin

    k_b: Boltzmann constant in eV/K

    hbar: reduced Planck constant in eV s

    n_v = 2*[mass_v*k_b*temp/(2pi*hbar^2)]^(3/2)
    '''

    kb_t = value('Boltzmann constant in eV/K') * temp
    h_bar = value('reduced Planck constant in eV s')

    n_v = 2*(mass_v*kb_t/(2*pi*(h_bar**2)))**(3/2)

    return n_v

def find_ni(n_0 = 0, p_0 = 0):
    '''
    Function to find the intrinsic carrier density

    n_0 = Equilibrium carrier density of electrons in conduction band

    p_0 = Equilibrium carrier density of holes in valence band

    n_i = sqrt(n_0*p_0)
    '''

    n_i = sqrt(n_0 * p_0)

    return n_i

def n0_from_ni(n_i=0,fermi_energy=0,in_fermi_level=0,temp=1):
    '''
    Function to find the equilibrium electron density from the
    intrinsic carrier density of a semiconductor.

    n_i: Intrinsic carrier density of a semiconductor

    fermi_energy: Fermi energy, or the chemical potential of an electron

    in_fermi_level: Fermi level of an intrinsic semiconductor

    temp: Temperature in Kelvin

    n0 = ni*exp((Ef-Efi)/kbT)
    '''
    kb_t = value('Boltzmann constant in eV/K') * temp
    exponential = exp((fermi_energy-in_fermi_level)/kb_t)
    n_0 = n_i*exponential

    return n_0

def p0_from_ni(n_i=0,fermi_energy=0,in_fermi_level=0,temp=1):
    '''
    Function to find the equilibrium hole density from the
    intrinsic carrier density of a semiconductor.

    n_i: Intrinsic carrier density of a semiconductor

    fermi_energy: Fermi energy, or the chemical potential of an electron

    in_fermi_level: Fermi level of an intrinsic semiconductor

    temp: Temperature in Kelvin

    p0 = ni*exp((Efi-Ef)/kbT)
    '''
    kb_t = value('Boltzmann constant in eV/K') * temp
    exponential = exp((in_fermi_level-fermi_energy)/kb_t)
    p_0 = n_i*exponential

    return p_0

def p0_approx(n_v=0,eta=0):
    '''
    Function to find the equilibrium hole density
    using an approximation of the Fermi-Dirac integral
    of order 1/2.

    n_v: The thermal effective density of states in the valence band.

    eta: (Ev - Ef)/kT
    '''
    p_0 = n_v*fdint_approx(eta)

    return p_0

def n0_approx(n_c=0,eta=0):
    '''
    Function to find the equilibrium electron density
    using an approximation of the Fermi-Dirac integral
    of order 1/2.

    n_c: The thermal effective density of states in the conduction band.

    eta: (Ef - Ec)/kT
    '''
    n_0 = n_c*fdint_approx(eta)

    return n_0


import math
import unittest

import numpy as np

def det(mat):
    """ det(a) --> d """
    assert isinstance(mat, np.ndarray)
    assert len(mat.shape) == 2
    assert mat.shape[0] == mat.shape[1]
    val_a, val_b, val_c, val_d = mat.reshape((-1,))
    return val_a * val_d - val_b * val_c

def det_recursive(mat):
    """ det(a)  = b1 * det (b) + b2 * det(c) + b3* det(d)
    𝒪(n!)
    """
    rows, _ = mat.shape
    if rows == 2:
        return det(mat)
    row = mat[0, :]
    det_mat = 0.0
    sign = 1
    for i, val in enumerate(row):
        # print(i, val)
        mat_i = np.zeros((rows-1, rows-1))
        mat_i[:, 0:i] = mat[1:, 0:i]
        mat_i[:, i:] = mat[1:, i+1:]
        # print(mat_i)
        det_i = det_recursive(mat_i)
        det_mat += sign * val * det_i
        sign *= -1
    return det_mat

def num_ops_det(num):
    if num == 1:
        num_ops = 1
    elif num == 2:
        num_ops = num_ops_det(num - 1) * num + (num - 1) + 1
    elif num == 3:
        num_ops = num_ops_det(num - 1) * num + (num - 1) + 1
    elif num >= 4:
        num_ops = num_ops_det(num - 1) * num + (num - 1) + 1
    else:
        num_ops = -1
    return num_ops

def demo():
    mat_a = np.array([[1, 2], [3, 4]])
    print(det(mat_a))

    mat_b = np.array([[789]])
    print(np.linalg.det(mat_b))

    det_num_ops = [num_ops_det(num) for num in range(2, 16)]
    print(det_num_ops)

    val_fact = [math.factorial(num) for num in range(2, 16)]
    print(val_fact)

    vec_det = np.array(det_num_ops, dtype=float)
    vec_fac = np.array(val_fact, dtype=float)

    print(vec_det/vec_fac)

class TestDeterminant(unittest.TestCase):

    def test_det_2x2(self):
        mat_a = np.random.randint(1, 10, size=(2, 2))
        nrows, ncols = mat_a.shape
        self.assertEqual(nrows, ncols)
        self.assertEqual(nrows, 2)

        val_a, val_b, val_c, val_d = mat_a.reshape((-1,))
        det_a = val_a * val_d - val_b * val_c
        self.assertEqual(det(mat_a), det_a)
        np.testing.assert_allclose(
            np.linalg.det(mat_a), det(mat_a))

    def test_det_lu_nxn(self):
        for num in range(3, 9+1):
            mat_a = np.random.randint(-5, 5, size=(num, num))
            mat_l, mat_u = np.tril(mat_a, k=-1) + np.eye(num, dtype=int), \
                np.triu(mat_a)
            mat_a = mat_l @ mat_u
            det_u = np.prod(np.diag(mat_u))
            np.testing.assert_allclose(np.linalg.det(mat_a), det_u, atol=1e-7)
            self.assertAlmostEqual(np.linalg.det(mat_a), det_u)

    def test_det_nxn(self):
        for num in range(3, 9+1):
            mat_a = np.random.randint(1, 10, size=(num, num))
            self.assertAlmostEqual(np.linalg.det(mat_a), det_recursive(mat_a))

if __name__ == '__main__':
    unittest.main(verbosity=2)


import matplotlib.pyplot as plt
import numpy as np
# Link - https://towardsdatascience.com/linear-regression-using-gradient-descent-97a6c8700931
# refer the github link too

#dataset from geeksforgeek

input_data=[0, 1, 2, 3, 4, 5, 6, 7, 8]
output_data=[1, 3, 2, 5, 7, 8, 8, 9, 10]
x=np.array(input_data)
y=np.array(output_data)                             #creating np arrays

t0=1
t1=1
alpha=0.01
m=float(len(x))
epochs=1000

for i in range(epochs):
    predicted_result=t0+(t1*x)
    diff0=sum(y-predicted_result)/(-2*m)
    diff1=sum((y-predicted_result)*x)/(-2*m)
    t0=t0-alpha*diff0
    t1=t1-alpha*diff1

print(t0,t1)

final_fitted_data=t0+(t1*x)
print(t0+(t1*9))
plt.scatter(x,y)
plt.plot([min(x),max(x)],[min(final_fitted_data),max(final_fitted_data)],color='red')
plt.show()


import numpy as np

def mean_squared_error(y, t):
    return (1 / 2) * np.sum((y - t) ** 2)

if __name__ == '__main__':
    t = np.array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0])

    y1 = np.array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0])
    y2 = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
    y3 = np.array([0.1, 0.9, 0.3, -0.3, 0, 0, 0.2, 0, 0, 0.6])

    print('t = {0}'.format(t))
    print('y1 = {0}, mean_squared_error = {1}'.format(
        y1, mean_squared_error(y1, t)))
    print('y2 = {0}, mean_squared_error = {1}'.format(
        y2, mean_squared_error(y2, t)))
    print('y3 = {0}, mean_squared_error = {1}'.format(
        y3, mean_squared_error(y3, t)))
    print('t = {0}, mean_squared_error = {1}'.format(
        t, mean_squared_error(t, t)))


import numpy as np

def sigmoid(x):
    return 1. / (1. + np.exp(-x))

def sigmoid_grad(x):
    sigmoid_grad = sigmoid(x)
    sigmoid_grad = sigmoid_grad * (1. - sigmoid_grad)
    return sigmoid_grad

def tanh(x):
    return np.tanh(x)

def tanh_grad(x):
    return (1. - (tanh(x) ** 2))

def relu(x):
    return np.maximum(0., x)

def relu_grad(x):
    gradient = np.ones(x.shape)
    gradient[x < 0] = 0
    return gradient

def softmax(x):
    e_x = np.exp(x - np.max(x, axis=-1).reshape(x.shape[0], 1))
    return e_x / np.sum(e_x, axis=1).reshape(e_x.shape[0], 1)

def softmax_grad(x):
    # This is a special case. It is easier to precompute the gradient wrt the activation and send it below
    return 1.

def linear(x):
    return x

def linear_grad(x):
    return 1.

def leaky_relu(x, alpha=0.01):
    mask = np.ones(x.shape)
    mask[x < 0] = alpha
    return x * mask

def leaky_relu_grad(x, alpha=0.01):
    mask = np.ones(x.shape)
    mask[x < 0] = alpha
    return mask


# 5g + 17m = 30.5
# 8g + 22m = 41

# Can be solved in NumPy using
import numpy as np

coeffs  = np.array([[5, 17], [8, 22]])
depvars = np.array([30.5, 41])
solution = np.linalg.solve(coeffs, depvars)

# solution
print ( solution )
# array([1, 1.5])
# means g=1, m=1.5 or 1 gallon per hour for generator,
# 1.5 gallon per hour for main moter


import numpy as np
import matplotlib.pyplot as plt

def permutation_test(first_array, second_array, resampling=30000, print_result=True, seed=0):

    np.random.seed = seed

    H0_diff = abs(first_array.mean() - second_array.mean())

    total_array = np.hstack([first_array, second_array])

    mean_diffs = []

    for _ in range(resampling):

        np.random.shuffle(total_array)

        random_first_array = total_array[:first_array.shape[0]]
        random_second_array = total_array[first_array.shape[0]:]

        mean_diff = abs(random_first_array.mean() - random_second_array.mean())
        mean_diffs.append(mean_diff)

    nb_extreme_values = 0

    for min_diff in mean_diffs:
        if min_diff > abs(H0_diff):
            nb_extreme_values += 1

    p_value = nb_extreme_values / len(mean_diffs)

    if print_result : print(f"P-value is {p_value:.2f}")
    return p_value


# -*- coding: utf-8 -*-
"""
Rotation operations
"""

import numpy as np

def x(phi):
    """
    returns rotation array along x-axis

    Parameters
    ----------
    phi: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array along x-axis
    """
    return np.array(((1, 0, 0), (0, np.cos(phi), -np.sin(phi)),
                     (0, np.sin(phi), np.cos(phi))), float)

def y(phi):
    """
    returns rotation array along y-axis

    Parameters
    ----------
    phi: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array along y-axis
    """
    return np.array(((np.cos(phi), 0, np.sin(phi)), (0, 1, 0),
                     (-np.sin(phi), 0, np.cos(phi))), float)

def z(phi):
    """
    returns rotation array along z-axis

    Parameters
    ----------
    phi: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array along z-axis
    """
    return np.array(((np.cos(phi), -np.sin(phi), 0),
                     (np.sin(phi), np.cos(phi), 0), (0, 0, 1)), float)

def z_2d(phi):
    """
    returns rotation array along z-axis for 2d case

    Parameters
    ----------
    phi: float
        rotation angle

    Returns
    -------
    res : (2,2)-np.ndarray
        rotation array along z-axis
    """
    return np.array(((np.cos(phi), -np.sin(phi)), (np.sin(phi), np.cos(phi))),
                    float)

def zyz(alpha, beta, gamma):
    """
    returns rotation array along z-axis y-axis z-axis

    Parameters
    ----------
    alpha: float
        rotation angle
    beta: float
        rotation angle
    gamma: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array
    """
    return np.dot(z(alpha), np.dot(y(beta), z(gamma)))

def zymz(theta, phi):
    """
    returns rotation array along z-axis y-axis -z-axis

    Parameters
    ----------
    theta: float
        rotation angle
    phi: float
        rotation angle

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array
    """
    return np.dot(z(phi), np.dot(y(theta), z(-phi)))

def a_on_b(a, b):
    """
    returns rotation array from vector a on vector b

    Parameters
    ----------
    a: np.ndarray
        vector to rotate
    b: np.ndarray
        vector to rotate on

    Returns
    -------
    res : (3,3)-np.ndarray
        rotation array
    """
    a = a / np.linalg.norm(a)
    b = b / np.linalg.norm(b)
    if np.all(a == b):
        return np.identity(3)

    v = np.cross(a, b)
    s = np.linalg.norm(v)
    c = np.dot(a, b)
    vx = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
    R = np.identity(3, float) + vx + np.dot(vx, vx) * (1 - c) / s**2
    return R


# -*- coding: utf-8 -*-
#%% NumPyの読み込み
import numpy as np
#   SciPyのstatsモジュールの読み込み
import scipy.stats as st
#   PyMCの読み込み
import pymc3 as pm
#   MatplotlibのPyplotモジュールの読み込み
import matplotlib.pyplot as plt
#   日本語フォントの設定
from matplotlib.font_manager import FontProperties
import sys
if sys.platform.startswith('win'):
    FontPath = 'C:\\Windows\\Fonts\\meiryo.ttc'
elif sys.platform.startswith('darwin' ):
    FontPath = '/System/Library/Fonts/ヒラギノ角ゴシック W4.ttc'
elif sys.platform.startswith('linux'):
    FontPath = '/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf'
else:
    sys.exit('このPythonコードが対応していないOSを使用しています．')
jpfont = FontProperties(fname=FontPath)
#%% ノイズを含むAR(1)過程からデータを生成
n = 500
np.random.seed(99)
x = np.empty(n)
x[0] = st.norm.rvs() # 定常分布の分散 = 0.19/(1 - 0.9**2) = 1.0
for t in range(1, n):
    x[t] = 0.9 * x[t-1] + st.norm.rvs(scale=np.sqrt(0.19))
y = x + st.norm.rvs(scale=0.5, size=n)
#%% 事後分布の設定
ar1_model = pm.Model()
with ar1_model:
    sigma = pm.HalfCauchy('sigma', beta=1.0)
    rho = pm.Uniform('rho', lower=-1.0, upper=1.0)
    omega = pm.HalfCauchy('omega', beta=1.0)
    ar1 = pm.AR('ar1', rho, sigma=omega, shape=n,
                init=pm.Normal.dist(sigma=omega/pm.math.sqrt(1 - rho**2)))
    observation = pm.Normal('y', mu=ar1, sigma=sigma, observed=y)
#%% 事後分布からのサンプリング
n_draws = 5000
n_chains = 4
n_tune = 1000
with ar1_model:
    trace = pm.sample(draws=n_draws, chains=n_chains, tune=n_tune,
                      random_seed=123)
    param_names = ['sigma', 'rho', 'omega']
    print(pm.summary(trace, var_names=param_names))
#%% 事後分布のグラフの作成
labels = ['$\\sigma$', '$\\rho$', '$\\omega$']
k = len(labels)
fig, ax = plt.subplots(k, 2, num=1, figsize=(8, 1.5*k), facecolor='w')
for index in range(k):
    mc_trace = trace[param_names[index]]
    x_min = mc_trace.min() - 0.2 * np.abs(mc_trace.min())
    x_max =  mc_trace.max() + 0.2 * np.abs(mc_trace.max())
    x = np.linspace(x_min, x_max, 250)
    posterior = st.gaussian_kde(mc_trace).evaluate(x)
    ax[index, 0].plot(mc_trace, 'k-', linewidth=0.1)
    ax[index, 0].set_xlim(1, n_draws*n_chains)
    ax[index, 0].set_ylabel(labels[index], fontproperties=jpfont)
    ax[index, 1].plot(x, posterior, 'k-')
    ax[index, 1].set_xlim(x_min, x_max)
    ax[index, 1].set_ylim(0, 1.1*posterior.max())
    ax[index, 1].set_ylabel('確率密度', fontproperties=jpfont)
ax[k-1, 0].set_xlabel('乱数系列', fontproperties=jpfont)
ax[k-1, 1].set_xlabel('周辺事後分布', fontproperties=jpfont)
plt.tight_layout()
plt.savefig('pybayes_fig_mcmc_ar1.png', dpi=300)
plt.show()


'''
Reference
[1] P. N. Belhumeur, J. P. Hespanha and D. J. Kriegman, "Eigenfaces vs. Fisherfaces: recognition using class specific linear projection," in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 19, no. 7, pp. 711-720, July 1997, doi: 10.1109/34.598228.
> The variable names used in this source code are consistent with this paper.
[2] Bhattacharyya, Suman & Rahul, Kumar. (2013). Face recognition by linear discriminant analysis. International Journal of Communication Network Security. 2. 31-35.
'''
import numpy as np
import sys

from settings import FACE_COUNT
from settings import IMAGE_COUNT_PER_FACE
from settings import TRAIN_COUNT_PER_FACE
from image_parser import read_all_images
from math_utils import get_dist

def get_W(training_set, m):
    assert len(training_set[0]) == TRAIN_COUNT_PER_FACE*FACE_COUNT
    n = len(training_set)
    '''
    The between-class scatter matrix is defined as S_B.
    The total scatter matrix is defined as S_T.
    The within-class scatter matrix is defined as S_W.

    '''
    S_B, S_T, S_W = np.zeros([n, n]), np.zeros([n, n]), np.zeros([n, n])
    '''
    mu is the mean image of all images in the training set.
    mu_i is is the mean image of the images of the i-th face (X_i).
    '''
    mu = np.array([np.mean(training_set, axis=1)]).T
    for i in range(FACE_COUNT):
        X_i = training_set[:, TRAIN_COUNT_PER_FACE *
                           i:TRAIN_COUNT_PER_FACE*(i+1)]
        mu_i = np.array([np.mean(X_i, axis=1)]).T
        S_B += np.dot(mu_i-mu, (mu_i-mu).T)*TRAIN_COUNT_PER_FACE
        for j in range(TRAIN_COUNT_PER_FACE):
            x_i_j = np.array([X_i[:, j]]).T
            S_T += np.dot(x_i_j-mu, (x_i_j-mu).T)
            S_W += np.dot(x_i_j-mu_i, (x_i_j-mu_i).T)
    '''
    S_W is always singular, for the rank of S_W is <= (N - c).
    N = TRAIN_COUNT_PER_FACE*FACE_COUNT (= 200), c = FACE_COUNT (=40); m <= (c-1) (=39).
    Fisherfaces is proposed to avoid this problem by projecting the images to a lower dimensional space.
    1. Use PCA to reduce the dimension of the feature space to (N - c);
    2. Applying the standard FLD to reduce the dimension to (c - 1).
    W_opt = W_pca * W_fld. W_opt is the most optimized W, that is, the return value.
    W_pca = argmax(X) det(W.T * S_T * W),
    performed over n * (N - c) matrices with orthonormal columns.
    W_fld = argmax(X) det(W.T * W_pca.T * S_B * W_pca * W) / det(W.T * W_pca.T * S_W * W_pca * W),
    performed over (N - c) * m matrices with orthonormal columns.

    W_pca is the set of eigenvectors of S_T corresponding to the (N - c) largest eigenvalues.
    S_T is a symmetric matrix, the purpose of calling `eigh` is to output eigenvectors without `+0.j`.
    '''
    eigenvalues_S_T, eigenvectors_S_T = np.linalg.eigh(S_T)
    sorted_eigenvalues_S_T_rank = np.argsort(eigenvalues_S_T)
    W_pca = []
    max_rank = len(eigenvalues_S_T)-1
    for i in range(TRAIN_COUNT_PER_FACE*FACE_COUNT-FACE_COUNT):
        rank = sorted_eigenvalues_S_T_rank[max_rank-i]
        if eigenvalues_S_T[i] == 0.0:
            break
        W_pca.append(eigenvectors_S_T[:, rank])
    W_pca = np.array(W_pca).T
    '''
    W_fld is the set of eigenvectors of inv(W_pca.T * S_W * W_pca) * W_pca.T * S_B * W_pca
    corresponding to the m largest eigenvalues.
    '''
    mat_fld_numerator = np.dot(np.dot(W_pca.T, S_B), W_pca)
    mat_fld_denominator = np.dot(np.dot(W_pca.T, S_W), W_pca)
    mat_fld = np.dot(np.linalg.inv(mat_fld_denominator), mat_fld_numerator)
    eigenvalues_fld, eigenvectors_fld = np.linalg.eig(mat_fld)
    sorted_eigenvalues_fld_rank = np.argsort(eigenvalues_fld)
    W_fld = []
    max_rank = len(eigenvalues_fld)-1
    assert m <= max_rank
    for i in range(m):
        rank = sorted_eigenvalues_fld_rank[max_rank-i]
        if eigenvalues_fld[i] == 0.0:
            break
        W_fld.append(eigenvectors_fld[:, rank])
    W_fld = np.array(W_fld).T
    '''
    Confirm that all elements of W_fld do not contain the imaginary part.
    Output W_fld without `+0.j`.
    '''
    if np.all(np.imag(W_fld) == 0.0):
        W_fld = np.real(W_fld)
    return np.dot(W_pca, W_fld)

def get_training_image_projections(W, training_images):
    assert len(W) == len(training_images)
    return np.dot(W.T, training_images)

def recognize_face(W, training_image_projections, face):
    assert len(training_image_projections[0]) == TRAIN_COUNT_PER_FACE * \
        FACE_COUNT and len(W) == len(face) and len(face[0]) == 1
    face_projection = np.dot(W.T, face)
    min_dist = get_dist(
        np.array([training_image_projections[:, 0]]).T, face_projection)
    face_recognized = 0
    for i in range(FACE_COUNT):
        for j in range(TRAIN_COUNT_PER_FACE):
            if i == 0 and j == 0:
                continue
            dist = get_dist(np.array(
                [training_image_projections[:, TRAIN_COUNT_PER_FACE*i+j]]).T, face_projection)
            if dist < min_dist:
                min_dist = dist
                face_recognized = i
    return face_recognized

if __name__ == '__main__':
    if len(sys.argv) == 1:
        print('Please specify a series of `m` via command line parameters.')
        print(f'0 < `m` < {FACE_COUNT}')
        print('E.g. python fld_recognizer.py 39 (...)')
        sys.exit()

    print('###### Fisher’s Linear Discriminant (FLD) Face Recognition ######')
    print('The face recognition results of each person are listed in the following format:')
    print('Face Number | Image Number | Face Recognized | Right or Wrong (T/F)\n...\nError Rate')
    training_images, test_images = read_all_images()
    for i in range(1, len(sys.argv)):
        m = int(sys.argv[i])
        print('################################################################################')
        print(f'The dimension of projections is set to {m}.')
        W = get_W(training_images, m)
        print('The projection matrix is as follows:')
        print(W)
        print(f'(Its shape: {len(W)} * {len(W[0])})')
        training_image_projections = get_training_image_projections(
            W, training_images)
        error_count = 0
        for j in range(FACE_COUNT):
            error_count_j = 0
            for k in range(IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE):
                face = np.array(
                    [test_images[:, (IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE)*j+k]]).T
                face_recognized = recognize_face(
                    W, training_image_projections, face)
                t_or_f = 'T'
                if face_recognized != j:
                    t_or_f = 'F'
                    error_count_j += 1
                print(f'{j+1}\t{k+1}\t{face_recognized+1}\t{t_or_f}')
            error_count += error_count_j
            print(f'{error_count_j} / {IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE} = {error_count_j/(IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE)}\n')
        print('Total Error Rate:')
        print(f'{error_count} / {(IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE)*FACE_COUNT} = {error_count/((IMAGE_COUNT_PER_FACE-TRAIN_COUNT_PER_FACE)*FACE_COUNT)}')
        print('################################################################################')