from bcipy.signal.process.filter import bandpass
import numpy as np
import matplotlib.pyplot as plt

'''Parameters entered for test here: '''
dcLevel = .1

# passband freq should be chosen w.r.t. filter specification, changes for filter to filter, defined in eegProcessor.py
fcos0 = 2
fcos1 = 10
fcos2 = 20
fcos3 = 30
fcos4 = 40

fs = 256
# fs = 300
# fs = 1024
Duration = 5 # in seconds for test duration
'''End of parameters'''

t = np.arange(0, Duration-1./fs, 1./fs) # time vector

# Cosines with different frequencies
x0 = np.cos(2*np.pi*fcos0*t)
x1 = np.cos(2*np.pi*fcos1*t)
x2 = np.cos(2*np.pi*fcos2*t)
x3 = np.cos(2*np.pi*fcos3*t)
x4 = np.cos(2*np.pi*fcos4*t)

x6 = dcLevel
x7 = np.cos(2*np.pi*1*t)
x8 = np.cos(2*np.pi*48*t)
x9 = np.cos(2*np.pi*70*t)
x10 = np.cos(2*np.pi*100*t)

xpassband = x0 + x1 + x2 + x3 + x4
xstopband = x6 + x7 + x8 + x9 + x10

# Two channels
x = np.zeros((2,xpassband.size))
x[0][:] = xpassband + xstopband
x[1][:] = xpassband + xstopband

y = bandpass.text_filter(x, fs = fs, k = 1)

MSE_perSample = np.sum((xpassband - y[0][:xpassband.size])**2.)/xpassband.size
MSE_perSample_norm = MSE_perSample/np.sum(x[0][:]**2)

print('MSE per sample: {} -> {}%'.format(MSE_perSample, MSE_perSample/5.*100))
print('MSE normalized: {}'.format(MSE_perSample_norm*100))

plt.figure(1)
plt.plot(t, xpassband, t,y[0][:xpassband.size])
plt.title('Sum of Pass-band Cosines')
plt.legend(('Pass-band signal', 'Filtered Signal'))
plt.show()

# plt.figure(2)
# plt.plot(t,y[0][:xpassband.size])
# plt.title('Filtered Signal')
# plt.show()

# plt.figure(3)
# plt.plot(t,x[0][:xpassband.size])
# plt.title('Received Signal')
# plt.show()


from numpy import fabs, arange

class Integration:
    EPS = 1e-6

    area_domain = list()
    area_codomain = list()

    @staticmethod
    def rectangles(func, a, b, iterations=180):
        delta_x = (b - a) / iterations
        x = [a + 0.5 * delta_x]
        y = [func(x[0])]
        current_area = 0.0
        for iteration in range(iterations):
            prev_area = current_area
            current_area += func(x[iteration]) * delta_x
            if fabs(current_area - prev_area) < Integration.EPS:
                break
            x.append(x[iteration] + delta_x)
            y.append(func(x[iteration+1]))
        # plt.plot(x, y, 'bs', color='red', label='Rectangles')
        return current_area

    @staticmethod
    def trapezoids(func, a, b, iterations=150):
        delta_x = (b - a) / iterations
        x = [a + 0.5 * delta_x]
        y = [func(x[0])]
        current_area = 0.0
        for iteration in range(iterations):
            current_area += func(x[iteration])
            x.append(x[iteration] + delta_x)
            y.append(func(x[iteration + 1]))
        # plt.plot(x, y, 'g^', color='yellow', label='Rectangles')
        return delta_x * ((func(a) + func(b)) / 2 + current_area)

    @staticmethod
    def simpson(func, a, b, iterations=50):
        delta_x = (b - a) / iterations
        Integration.area_domain.extend(arange(a, b, delta_x))
        x = a
        current_area = func(a) + func(b)
        for iteration in range(iterations):
            x += delta_x
            if iteration % 2:
                current_area += 2.0 * func(x)
            else:
                current_area += 4.0 * func(x)
            Integration.area_codomain.append((delta_x / 3) * current_area)
        return (delta_x/3.0)*current_area


# -*- coding: utf-8 -*-
import numpy as np

def pca(X, k):
    '''X: m*n,m个样本，每个样本有n个特征
       k: 降到k维
    '''
    m, n = X.shape
    if k>n: return X
    X = X - X.mean(axis=0) ## 中心化
    cov = np.cov(X,rowvar=False,ddof=m-1) ## 协方差矩阵系数为1
    val, vec = np.linalg.eig(cov) ## 特征值+特征向量
    vec = vec[:,np.argpartition(-val,k-(k==n))[:k]] ## 取前k个最大特征值对应的特征向量
    return X.dot(vec) ## 返回降维后的X

if __name__=='__main__':

    import matplotlib.pyplot as plt
    X = np.array([(2.5,2.4), (0.5,0.7), (2.2,2.9), (1.9,2.2), (3.1,3.0),
                  (2.3, 2.7), (2, 1.6), (1, 1.1), (1.5, 1.6), (1.1, 0.9)])
    X1 = pca(X,1)
    X2 = pca(X,2)
    plt.scatter(X[:,0],X[:,1], marker='o',c='red')
    plt.scatter(X1,np.zeros(len(X1)), marker='o',c='blue')
    plt.scatter(X2[:,0],X2[:,1], marker='*',c='blue')


# Disable debbuging logs (to get rid of cuda warnings)
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'

from scipy import signal as sg

I = [[255, 7, 3],
     [212, 240, 4],
     [218, 216, 230]]

g = [[-1,1]]

print('Without zero padding \n')
print('{0} \n'.format(sg.convolve(I, g, 'valid')))
'''
output:
[[248   4]
 [-28 236]
 [  2 -14]]
'''

'''
The 'valid' argument states that the output consists
only of those elements that do not rely on the
zero-padding
'''

print ('With zero padding \n')
print ('{0} \n'.format(sg.convolve(I, g, 'full')))

'''
output
[[-255  248    4    3]
 [-212  -28  236    4]
 [-218    2  -14  230]]
'''

print ('Without zero padding_same \n')
print ('{0} \n'.format(sg.convolve( I, g, 'same')))

'''
This output is full discrete linear convultion of the inputs.
It will use zero to complete the input matrix
'''


import numpy as np
import itertools
import matplotlib.pyplot as plt
import csv
import sys
import math
import geopy.distance

def create_nodes_array(N, seed=None):
    """
    Creates array of random points of size N.
    """
    if seed:
        print("seed", seed)
        np.random.seed(seed)

    nodes_list = []
    for i in range(N):
        nodes_list.append(np.random.rand(2) * 10)
    return np.array(nodes_list)

def readInstance(instance):
    """
    Creates array from a TSP instance.
    """
    try:
        reader = csv.reader(open(instance), delimiter=' ')
    except FileNotFoundError:
        sys.exit("Fichero param.config no encontrado")

    nodes_list = []

    nodes=False

    for row in reader:

        if row[0]=="EOF":
            break
        if nodes==True:
            node = [float(row[1]),float(row[2])]
            nodes_list.append(node)

        if row[0]=="NODE_COORD_SECTION":
           nodes=True

    return np.array(nodes_list)

def get_tsp_matrix(nodes_array):
    """
    Creates distance matrix out of given coordinates.

    """

    number_of_nodes = len(nodes_array)
    matrix = np.zeros((number_of_nodes, number_of_nodes))
    for i in range(number_of_nodes):
        for j in range(i, number_of_nodes):
            #matrix[i][j] = distance_between_points(nodes_array[i], nodes_array[j])
            matrix[i][j] = distance_two_coordinates(nodes_array[i], nodes_array[j])
            matrix[j][i] = matrix[i][j]
    return matrix

def distance_between_points(point_A, point_B):
    return np.sqrt((point_A[0] - point_B[0])**2 + (point_A[1] - point_B[1])**2)

def distance_two_coordinates(point_A, point_B):

    lat1 = math.pi * ((point_A[0] // 1) + 5.0 * (point_A[0] % 1) / 3.0) / 180.0
    lon1 = math.pi * ((point_A[1] // 1) + 5.0 * (point_A[1] % 1) / 3.0) / 180.0
    lat2 = math.pi * ((point_B[0] // 1) + 5.0 * (point_B[0] % 1) / 3.0) / 180.0
    lon2 = math.pi * ((point_B[1] // 1) + 5.0 * (point_B[1] % 1) / 3.0) / 180.0

    q1       = math.cos( lon1 - lon2 )
    q2       = math.cos( lat1 - lat2 )
    q3       = math.cos( lat1 + lat2 )
    radius   = 6378.388

    distance = radius * math.acos (1/2 * ((1 + q1) * q2 - (1 - q1) * q3)) + 1

    if distance==1.0:
        return 0

    return(int(distance))

    #return round(distance)
    #return round(geopy.distance.great_circle(point_A, point_B).km)

def solve_tsp_brute_force(nodes_array):
    number_of_nodes = len(nodes_array)
    initial_order = range(0, number_of_nodes)
    all_permutations = [list(x) for x in itertools.permutations(initial_order)]
    cost_matrix = get_tsp_matrix(nodes_array)
    best_permutation = all_permutations[0]
    best_cost = calculate_cost(cost_matrix, all_permutations[0])
    for permutation in all_permutations:
        current_cost = calculate_cost(cost_matrix, permutation)
        if current_cost < best_cost:
            best_permutation = permutation
            best_cost = current_cost
    print("Brute force:", best_permutation, best_cost)
    return best_permutation

def solve_tsp_brute_force_from_given_node(nodes_array, starting_node):
    number_of_nodes = len(nodes_array)
    initial_order = range(0, number_of_nodes)
    all_permutations = [list(x) for x in itertools.permutations(initial_order)]
    cost_matrix = get_tsp_matrix(nodes_array)
    best_permutation = all_permutations[0]
    best_cost = calculate_cost(cost_matrix, all_permutations[0])*1000
    for permutation in all_permutations:
        if permutation[0] != starting_node:
            continue
        current_cost = calculate_cost(cost_matrix, permutation)
        if current_cost < best_cost:
            best_permutation = permutation
            best_cost = current_cost
    print("Brute force:", best_permutation, best_cost)
    return best_permutation

def calculate_cost(cost_matrix, solution):
    cost = 0
    for i in range(len(solution)):
        a = i%len(solution)
        b = (i+1)%len(solution)
        cost += cost_matrix[solution[a]][solution[b]]

    return cost

def plot_solution(name, node_array, solution):
    nodes_array = np.array(node_array)
    plt.scatter(nodes_array[:, 0], nodes_array[:, 1], s=200)
    for i in range(len(nodes_array)):
        plt.annotate(i, (nodes_array[i, 0] + 0.15, nodes_array[i, 1] + 0.15), size=16, color='r')

    plt.xlim([min(nodes_array[:, 0]) - 1, max(nodes_array[:, 0]) + 1])
    plt.ylim([min(nodes_array[:, 1]) - 1, max(nodes_array[:, 1]) + 1])
    for i in range(len(solution)):
        a = i%len(solution)
        b = (i+1)%len(solution)
        A = solution[a]
        B = solution[b]
        plt.plot([nodes_array[A, 0], nodes_array[B, 0]], [nodes_array[A, 1], nodes_array[B, 1]], c='r')

    cost = calculate_cost(get_tsp_matrix(nodes_array), solution)
    title_string = "Cost:" + str(cost)
    title_string += "\n" + str(solution)
    plt.title(title_string)
    plt.savefig('figures/' + name + '.png')
    plt.clf()

def points_order_to_binary_state(points_order):
    """
    Transforms the order of points from the standard representation: [0, 1, 2],
    to the binary one: [1,0,0,0,1,0,0,0,1]
    """
    number_of_points = len(points_order)
    binary_state = np.zeros((len(points_order) - 1)**2)
    for j in range(1, len(points_order)):
        p = points_order[j]
        binary_state[(number_of_points - 1) * (j - 1) + (p - 1)] = 1
    return binary_state

def binary_state_to_points_order(binary_state):
    """
    Transforms the the order of points from the binary representation: [1,0,0,0,1,0,0,0,1],
    to the binary one: [0, 1, 2]
    """
    points_order = []
    number_of_points = int(np.sqrt(len(binary_state)))
    for p in range(number_of_points):
        for j in range(number_of_points):
            if binary_state[(number_of_points) * p + j] == 1:
                points_order.append(j)
    return points_order


import numpy as np
from bootstrap_analyses.boostrap_statistical_analyses.one_sample.submodules.permutation import draw_perm_reps

def diff_between_series(series1, series2, reducer=np.mean):

    return reducer(series1) - reducer(series2)

def frac_of_cases(series1, series2=None):
    """
    Description:

        Compute fraction True cases from given data series

    Params:
        series1: an array of True and False cases:

        series2: it is not used. It is only there,
                 so that the draw_perm_reps can work without major changes

    """
    frac = series1[series1==True].size / (series1.size)
    return frac

def diff_frac(data_A, data_B):
    frac_A = frac_of_cases(data_A)
    frac_B = frac_of_cases(data_B)

    return frac_B - frac_A

def eval_permutation_pvalue(series_A, series_B,
                            reducer=diff_frac,
                            n_samples=10000):

    diff_frac_obs = reducer(series_A, series_B)

    perm_replicates = np.empty(n_samples)

    for i in range(n_samples):
        perm_replicates[i] = draw_perm_reps(series_A,
                                            series_B,
                                            reducer,
                                            size=1)

    p_value = np.mean(perm_replicates >= diff_frac_obs)

    return {'p_value':p_value, 'perm_replicates':perm_replicates,'empirical_diff':diff_frac_obs}

if '__main__' == __name__:
    print(''' example case of AB test:

        You measure the number of people who click on an ad on your
        company's website before and after changing its color.

        prior: the series of clicks (True, False) prior to the change

        after: the series of clicks (True, False) after the change

        The sum of True represents the amount of clicks on the ad during the
        time evaluated.

        A True represents someone that did click on your ad
        A False represents someone that did not click on your ad

         ''')

    prior = np.array([True] * 153 + [False] * 91)
    after = np.array([True] * 136 + [False] * 35)

    results = eval_permutation_pvalue(prior, after, reducer=diff_frac)

    print('p-value =', results['p_value'])

    ################

    n = '\n'*2 + '-'*40 + '\n'*2
    print(n, """Second case:

              Assume you measure the values of a given variable before and after a given
              update in your sistem.

              To measure the probability of that change having any effect over
              the variable of interest, let's say:
                  The amount of time a person lasts in your website

              We could do a permutation pvalue analyzes as follows:

                  series 1: an array of entries, which each entry represents the amount of time a given
                  person spent in your website prior to a given change

                  series 2: an array of entries, which each entry represents the amout of time
                  that a given person spent after the given change.

              For this particular case, since it is no longer a True/False condition,
              the function to evaluate the potential difference between the two series
              would be something like: diff_between_series case, in which one
              could evaluate the difference of a given parameter of interest
              between the series:

         """,n)

    series1 = np.random.normal(1,2,size=100)
    series2 = np.random.normal(3, 4, size=100)

    reducers = [np.mean, np.std, np.var, lambda x: np.percentile(x, 25)]

    for reducer in reducers:

        results = eval_permutation_pvalue(prior, after, reducer=diff_frac)

        print(reducer.__name__, 'pvalue: {0:.3f}'.format( results['p_value']))


'''
Container for activation functions.

Largely copied from neat-python. (Copyright 2015-2017, CodeReclaimers, LLC.)
'''
import math
import types
import numpy as np

def softmax(x):
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

def sigmoid_activation(x):
    return 1 / (1+math.exp(-x))

def tanh_activation(x):
    return math.tanh(x)

def sin_activation(x):
    return math.sin(x)

def tan_activation(z):
    return math.tan(z)

def cos_activation(z):
    return math.cos(z)

def gauss_activation(z):
    '''
    Bounding input within 3.4 standard deviations.
    '''
    z = max(-3.4, min(3.4, z))
    return math.exp(-5.0 * z**2)

def sharp_gauss_activation(x):
    mu = 0
    z = abs(x)
    return math.exp(-100.0 * (x-mu)**2)

def sharp_gauss_mu_2_activation(x):
    mu = 2
    return math.exp(-100.0 * (x-mu)**2)

def relu_activation(x):
    return x if x > 0.0 else 0.0

def log_activation(x):
    z = max(1e-7, x)
    return math.log(x)

def exp_activation(x):
    x = max(-60.0, min(60.0, z))
    return math.exp(x)

def linear_activation(x):
    return x

class InvalidActivationFunction(TypeError):
    pass

def validate_activation(function):
    if not isinstance(function,
                      (types.BuiltinFunctionType,
                       types.FunctionType,
                       types.LambdaType)):
        raise InvalidActivationFunction("A function object is required.")
    if function.__code__.co_argcount != 1: # avoid deprecated use of `inspect`
        raise InvalidActivationFunction("A single-argument function is required.")

class ActivationFunctionSet(object):
    """
    Contains the list of current valid activation functions,
    including methods for adding and getting them.
    """
    def __init__(self):
        self.functions = {}
        self.add('sigmoid', sigmoid_activation)
        self.add('sin', sin_activation)
        self.add('cos', cos_activation)
        self.add('relu', relu_activation)
        self.add('linear', linear_activation)
        self.add('gauss', gauss_activation)
        self.add('sharp_gauss', sharp_gauss_activation)
        self.add('sharp_gauss2', sharp_gauss_mu_2_activation)

    def add(self, name, function):
        validate_activation(function)
        self.functions[name] = function

    def get(self, name):
        f = self.functions.get(name)
        if f is None:
            raise InvalidActivationFunction("No such activation function: {0!r}".format(name))
        return f


#!/usr/bin/env python

# Importing libraries
import sys, os, cv2, joblib, argparse
sys.path.append(os.path.join(".."))
import utils.classifier_utils as clf_util
import numpy as np
import pandas as pd
from sklearn import metrics
from sklearn import datasets
from sklearn.datasets import fetch_openml
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelBinarizer
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import classification_report
from sklearn.metrics import accuracy_score

# Defining main function
def main(outname, save, individual, penalty, c):

    # Importing data; y = what the image depicts, X = values for all pixels (from top right, moving left)
    print("[INFO] Loading the MNIST dataset ...")
    X, y = fetch_openml('mnist_784', version = 1, return_X_y = True)

    # Converting to numpy arrays
    X = np.array(X)
    y = np.array(y)

    # Make a test-train split of some of the data
    X_train, X_test, y_train, y_test = train_test_split(X,
                                                        y,
                                                        random_state = 9, # for replication purposes
                                                        train_size = 20000, # absolute size of test and train set to avoid too much data
                                                        test_size = 2000)

    # Min-max scaling:
    scaler = MinMaxScaler()
    scaler = scaler.fit(X_train) # Important to scale not only train data but also test data information from train
    X_train_scaled = pd.DataFrame(scaler.transform(X_train))
    X_test_scaled = pd.DataFrame(scaler.transform(X_test))

    # Fitting a model to the training data
    print("[INFO] Training a logistic regression classifier ...")
    clf = LogisticRegression(penalty = penalty, # Type of penalty, if any
                            tol = 0.1, # Tolerance for stopping
                            solver = 'saga', # Solver method
                            C = c, # C-parameter value (smaller value stronger regularization)
                            multi_class = 'multinomial').fit(X_train_scaled, y_train) # Multiclass classification

    # Predicting the test data, using the model fitted on the training data
    print("[INFO] Evaluating the logistic classifier ...")
    y_pred = clf.predict(X_test_scaled)

    # Get classification report as a data frame
    classif_report = pd.DataFrame(metrics.classification_report(y_test, y_pred, output_dict = True))

    # Print to terminal
    print(classif_report)

    # Save as csv in "out"-folder, if save == True
    if save == True:
        # If the folder does not already exist, create it
        if not os.path.exists("out"):
            os.makedirs("out")

        # Create outpath and save
        outpath = os.path.join("out", outname)
        classif_report.to_csv(outpath, index = False)
        print(f"[INFO] The classification benchmark report has been saved: \"{outpath}\".")

        # Saving model
        outpath_lr_model = os.path.join("out", "lr_model.pkl")
        joblib.dump(clf, outpath_lr_model)
        print(f"[INFO] The trained logistic regression classifier model has been saved: \"{outpath_lr_model}\".")

    # If the argument for individual prediction is not "None", then predict the individual image
    if individual != None:

        # Get the possible labels, as well as the number of possible labels
        classes = sorted(set(y))
        nclasses = len(classes)

        # Read individual image and convert to grayscale
        image = cv2.imread(individual)
        gray = cv2.bitwise_not(cv2.cvtColor(image, cv2.COLOR_BGR2GRAY))

        # Compress image to fit the input size of the model
        compressed = cv2.resize(gray, (28, 28), interpolation=cv2.INTER_AREA)

        # Predict the individual image, using the previously trained model
        individual_pred = clf_util.predict_unseen_assignment4(compressed, clf, classes)
        print(f"[IMAGE PREDICTION] Image prediction for \"{individual}\": {individual_pred}")

# Define behaviour when called from command line
if __name__=="__main__":
    # Initialize ArgumentParser class
    parser = argparse.ArgumentParser(
        description = "Script that trains a linear regression classifier on a subset of the mnist dataset. Tests on another part of the mnist dataset and outputs classification report. Can also be used to predict individual images, using the argument --individual.")

    # Add outname argument
    parser.add_argument(
        "-o",
        "--outname",
        type = str,
        default = "classif_report_logistic_regression.csv", # Default when not specifying name of outputfile
        required = False, # Since we have a default value, it is not required to specify this argument
        help = "str - containing name of classification report.")

    # Add save argument
    parser.add_argument(
        "-s",
        "--save",
        type = bool,
        default = True, # Default when not specifying anything else
        required = False, # Since we have a default value, it is not required to specify this argument
        help = "bool - specifying whether to save classification report.")

    # Add individual image prediction argument
    parser.add_argument(
        "-i",
        "--individual",
        type = str,
        default = os.path.join("..", "data", "cf_test", "test.png"), # Default when not specifying anything in the terminal
        required = False, # Since we have a default value, it is not required to specify this argument
        help = "str - specifying a .png file which is to be classified using this logistic regression model.")

    # Add penalty argument
    parser.add_argument(
        "-p",
        "--penalty",
        type = str,
        default = "none", # Default when not specifying anything in the terminal
        required = False, # Since we have a default value, it is not required to specify this argument
        help = "str - specifying penalty for the classifier - possible values: \"none\", \"l1\", \"l2\", \"elasticnet\"")

    # Add c-parameter argument
    parser.add_argument(
        "-c",
        "--c",
        type = float,
        default = 1.0, # Default when not specifying anything in the terminal
        required = False, # Since we have a default value, it is not required to specify this argument
        help = "int - specifying c-parameter for the classifier. recommended values: 0.01, 0.1, 1.0, 10, 100, where lower values mean stronger regularization")

    # Taking all the arguments we added to the parser and input into "args"
    args = parser.parse_args()

    # Execute main function
    main(args.outname, args.save, args.individual, args.penalty, args.c)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Mar 13 15:39:22 2019

@author: alankar
"""

import numpy as np
import matplotlib.pyplot as plt
from basic_units import radians, sin

"""
u = np.random.uniform(low=0.,high=1.,size=1000)
x = np.piecewise(u,[u<=0.5],[lambda u: np.arccos(1-2*u),lambda u: np.pi-np.arccos(2*u-1)])
plt.hist(x,normed=True)
x = np.linspace(0,np.pi,100)
plt.plot(x,0.5*np.sin(x))
"""

def azim_draw(N):#radians
    u = np.random.uniform(low=0.,high=2*np.pi,size=N)
    return u

def polar_draw(N):#radians
    u = np.random.uniform(low=0.,high=1.,size=N)
    x = np.piecewise(u,[u<=0.5],[lambda u: np.arccos(1-2*u),lambda u: np.pi-np.arccos(2*u-1)])
    return x

phi = [val*radians for val in np.arange(0, 2*np.pi, 0.01)]
theta = [val*radians for val in np.arange(0, np.pi, 0.01)]
N = int(1e5)

plt.figure(figsize=(13,10))
plt.plot(phi,(1/(2*np.pi))*np.ones(len(phi)),xunits=radians,color='black')
plt.hist(azim_draw(N),normed=True,bins=20,color='orange')
plt.grid()
plt.ylabel(r'Probability',size=18)
plt.xlabel(r'Angle (rad)',size=20)
plt.title(r'Distribution of Azimuthal Angle', size=21)
plt.tick_params(axis='both', which='major', labelsize=18)
plt.tick_params(axis='both', which='minor', labelsize=15)
plt.savefig('10_1.png')
plt.show()

plt.figure(figsize=(13,10))
plt.plot(theta,np.array(sin(theta))/2,xunits=radians,color='black')
plt.hist(polar_draw(N),normed=True,bins=20,color='orange')
plt.grid()
plt.ylabel(r'Probability',size=18)
plt.xlabel(r'Angle (rad)',size=20)
plt.title(r'Distribution of Polar Angle', size=21)
plt.tick_params(axis='both', which='major', labelsize=18)
plt.tick_params(axis='both', which='minor', labelsize=15)
plt.savefig('10_2.png')
plt.show()


# poly
# The poly tool returns the coefficients of a polynomial with the given sequence of roots.
# print numpy.poly([-1, 1, 1, 10])        #Output : [  1 -11   9  11 -10]

# roots
# The roots tool returns the roots of a polynomial with the given coefficients.
# print numpy.roots([1, 0, -1])           #Output : [-1.  1.]

# polyint
# The polyint tool returns an antiderivative (indefinite integral) of a polynomial.
# print numpy.polyint([1, 1, 1])          #Output : [ 0.33333333  0.5         1.          0.        ]

# polyder
# The polyder tool returns the derivative of the specified order of a polynomial.
# print numpy.polyder([1, 1, 1, 1])       #Output : [3 2 1]

# polyval
# The polyval tool evaluates the polynomial at specific value.
# print numpy.polyval([1, -2, 0, 2], 4)   #Output : 34

# polyfit
# The polyfit tool fits a polynomial of a specified order to a set of data using a least-squares approach.
# print numpy.polyfit([0,1,-1, 2, -2], [0,1,1, 4, 4], 2)
# #Output : [  1.00000000e+00   0.00000000e+00  -3.97205465e-16]

# Task :
# You are given the coefficients of a polynomial.
# Your task is to find the value of at point .

import numpy as np

P = list(map(float, input().split()))
P = np.array(P)
x = int(input())
print(np.polyval(P, x), end="")

# Sample Input
# 1.1 2 3
# 0

# Sample Output
# 3.0


import numpy as np
import matplotlib.pyplot as plt

class SolveMinProbl:
    def __init__(self, y, A):  # 111 and identity matrix
        self.matr = A  # matrix A allocating memory for parameters ST we don't need to call
        self.Np = y.shape[0]  # rows shape tell us the shape of the matrix
        self.Nf = A.shape[1]  # columns
        self.vect = y  # col vector
        self.sol = np.zeros((self.Nf, 1), dtype=float)  # column vector w solution
        self.min = 0.0
        self.err = 0
        return
    def plot_w(self,title = 'solution'):
        w = self.sol  # already initialized self.sol in the previous method
        n = np.arange(self.Nf)
        plt.figure()
        plt.plot(n, w)
        plt.xlabel('n')
        plt.ylabel('w(n)')
        plt.grid()
        plt.title(title)
        plt.show()
        return
    def print_result(self , title):
        print(title, ' :')
        print('the optimum weight vector is: ')
        print(self.sol)  # w
        return
    def plot_err(self, title='Square error', logy=1, logx=0):
        err = self.err
        plt.figure()
        if(logy == 0) & (logx == 0):
            plt.plot(err[:, 0], err[:, 1])
        if (logy == 1) & (logx == 0):
            plt.semilogy(err[:, 0], err[:, 1])
        if (logy == 0) & (logx == 1):
            plt.semilogx(err[:, 0], err[:, 1])
        if (logy == 1) & (logx == 1):
            plt.loglog(err[:, 0], err[:, 1])
        plt.xlabel('n')
        plt.ylabel('e(n)')
        plt.title(title)
        plt.margins(0.01, 0.1)
        plt.grid()
        plt.show()
        return
class SolveLLS(SolveMinProbl):  # this class belongs to SolveMinProbl
    def run(self):  # the inpu
        A = self.matr
        y = self.vect
        w = np.dot(np.dot(np.linalg.inv(np.dot(A.T, A)), A.T), y)
        self.sol = w
        self.min = np.linalg.norm(np.dot(A, w)-y)
class SolveGrad(SolveMinProbl):
    def run(self, gamma, Nit):
        self.err = np.zeros((Nit, 2), dtype=float)
        A = self.matr
        y = self.vect
        w = np.random.rand(self.Nf, 1)  # uniform pdf random var range 0,1
        for it in range(Nit):
            grad = 2*np.dot(A.T, (np.dot(A, w)-y))
            w = w - gamma * grad
            self.err[it, 0] = it
            self.err[it, 1] = np.linalg.norm(np.dot(A, w)-y)**2/self.Np
        self.sol = w
        self.min = self.err[it, 1]
class SolveSteepDesc(SolveMinProbl):
    def run(self, Nit):
        self.err = np.zeros((Nit, 2), dtype=float)
        A = self.matr
        y = self.vect
        w = np.random.rand(self.Nf, 1)  # uniform pdf random var range 0,1
        it = 0
        for it in range(Nit):
            grad = 2 * np.dot(A.T, (np.dot(A, w) - y))
            H = 4 * np.dot(A.T, A)
            gamma2 = (np.linalg.norm(grad)**2)/np.dot(np.dot(grad.T, H), grad)
            w = w - gamma2 * grad
            self.err[it, 0] = it
            self.err[it, 1] = np.linalg.norm(np.dot(A, w) - y)**2/self.Np
        self.sol = w
        self.min = self.err[it, 1]
class SolveStoch(SolveMinProbl):
    def run(self, Nit, gamma):
        self.err = np.zeros((Nit, 2), dtype=float)
        A = self.matr
        y = self.vect
        w = np.random.rand(self.Nf, 1)  # uniform pdf random var range 0,1
        it = 0
        row = np.zeros((1, self.Nf), dtype=float)
        for it in range(Nit):
            for i in range(self.Np):
                for j in range(self.Nf):
                    row[0, j] = A[i, j]
                grad = 2 * np.dot(row.T,(np.dot(row, w) - y[i]))
                w = w - gamma * grad
            self.err[it, 0] = it
            self.err[it, 1] = np.linalg.norm(np.dot(A, w) - y)**2/self.Np
        self.sol = w
        self.min = self.err[it, 1]
class SolveMini(SolveMinProbl):
    def run(self, Nit, N, gamma): #N is number of minibatches
        if (N > self.Np) | (self.Np % N != 0):
            print("function not valid")
        self.err = np.zeros((Nit, 2), dtype=float)
        A = self.matr
        y = self.vect
        w = np.random.rand(self.Nf, 1)  # uniform pdf random var range 0,1
        it = 0
        m = int(self.Np/N)
        for it in range(Nit):
            for i in range(self.Nf):
                for j in range(N):
                    grad = 2 * np.dot(A[j:(j*m+m), :].T, (np.dot(A[j:(j*m+m), :], w) - y[j:(j*m+m)]))
                    w = w - gamma * grad
            self.err[it, 0] = it
            self.err[it, 1] = np.linalg.norm(np.dot(A, w) - y)**2/self.Np
        self.sol = w
        self.min = self.err[it, 1]
class SolveConj(SolveMinProbl):
    def run(self):
        self.err = np.zeros((self.Nf, 2), dtype=float)
        A = self.matr
        y = self.vect
        w = np.zeros((self.Nf, 1), dtype=float)
        it = 0
        Q = 2 * np.dot(A.T, A)
        b = 2 * np.dot(A.T, y)
        g = -b
        d = -g
        for it in range(self.Nf):
            #if np.dot(np.dot(d.T, Q), d) == 0:
            #   break
            a = -1 * np.dot(d.T, g)/np.dot(np.dot(d.T, Q), d)
            w = w + a * d
            g = g + a * np.dot(Q, d)
            beta = np.dot(np.dot(g.T, Q), d)/np.dot(np.dot(d.T, Q), d)
            d = -1 * g + beta*d
            self.err[it, 0] = it
            self.err[it, 1] = np.linalg.norm(np.dot(A, w) - y)**2/self.Np
        self.sol = w
        self.min = self.err[it, 1]

class SolveRidge(SolveMinProbl):
    def run(self, lamb=0.5):
        A = self.matr
        y = self.vect
        I = np.eye(self.Nf)
        w = np.dot(np.dot(np.linalg.inv(np.dot(A.T, A)+lamb*I), A.T), y)
        self.sol = w
        self.min = np.linalg.norm(np.dot(A, w) - y)


import numpy as np
from scipy.spatial.distance import cdist
import random

random.seed(25)

class KMeans:
    def __init__(
            self,
            k: int,
            metric: str = "euclidean",
            tol: float = 1e-6,
            max_iter: int = 100):
        """
        inputs:
            k: int
                the number of centroids to use in cluster fitting
            metric: str
                the name of the distance metric to use
            tol: float
                the minimum error tolerance from previous error during optimization to quit the model fit
            max_iter: int
                the maximum number of iterations before quitting model fit
        """
        self.k = k
        self.metric = metric
        self.tol = tol
        self.max_iter = max_iter
        self.centroids = [] # Stores centroids
        self.labels = [] # Stores cluster labels for all points

        assert k>1, "Number of clusters to form (k) must be at least 2"

    def _reinit_labels(self):
        """
        resets cluster labels for all points

        """

        self.labels = []

        return

    def _assign(self, num_obsv: int, dists: np.ndarray, mat: np.ndarray):
        """
        assigns observations in dataset to their closest cluster by referencing a
        precalculated matrix of distances between cluster centroids and observations

        inputs:
            num_obsv: int
                The number of observations in dataset

            dists: np.ndarray
                A 2D matrix where the rows are observations and columns are clusters.
                The i,jth entry represents the Euclidean distance between observation i
                and the centroid of cluster j

            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features.
        """
        for m in range(num_obsv):

            # Retrieve distances between observation m and current centroids
            m_dist = dists[m].tolist()

            # Figure out which centroid is currently closest to observation m
            c = m_dist.index(min(m_dist))

            # Label observation m as belonging to the cluster of the centroid to which it's closest
            self.labels.append(c)

        return

    def _recalc_centroids(self, num_fts: int, mat: np.ndarray):
        """
        helper function for the kmeans clustering algorithm that computes new centroids
        based on current cluster assignments

        inputs:

            num_fts: int
                The number of features in each observation

            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features.

        outputs:
            np.ndarray
                a 2D array with updated centroids for the model
        """
        new_centroids = np.empty(shape=(self.k, num_fts))

        for c in range(self.k):

            points_indices = np.where(np.asarray(self.labels) == c)[0]

            # If no points have been assigned to this cluster, the centroid doesn't
            # have to be updated.
            if len(points_indices) == 0:
                new_centroids[c] = self.centroids[c]

            else:

                # Calculate the new centroid in each cluster
                points = mat[points_indices]
                cluster_size = points.shape[0]
                new_centroids[c] = np.sum(points, axis=0)/cluster_size

        # Return the new centroids
        return new_centroids

    def fit(self, mat: np.ndarray):
        """
        fits the kmeans algorithm onto a provided 2D matrix

        inputs:
            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features
        """

        self._reinit_labels() # Reinitialize empty labels in case function is run multiple times

        num_obsv = mat.shape[0]
        num_fts = mat.shape[1]

        assert self.k <= num_obsv, "Number of clusters to form exceeds number of observations in dataset"

        # Initialize an empty matrix (k X m) to store centroid locations
        self.centroids = np.empty(shape=(self.k, num_fts))

        # Randomly initialize k centroids
        for i in range(self.k):
            self.centroids[i] = mat[random.randint(0, num_obsv-1)]

        iter_counter = 0

        while True:

            # Compute initial error
            old_error = self.get_error(mat)

            # Reset labels
            self._reinit_labels()

            # Calculate Euclidean distance between all points and existing clusters.
            dists = cdist(mat, self.centroids, self.metric)

            # Iterate through observations, assigning each to the closest cluster
            self._assign(num_obsv, dists, mat)

            # Recalculate centroids after reassigning all points
            self.centroids = self._recalc_centroids(num_fts, mat)

            iter_counter+=1

            # Once we have iterated a maximum number of times OR stopped improving, end loop.
            if (iter_counter >= self.max_iter) or ((old_error - self.get_error(mat)) < self.tol):
                break

        return

    def predict(self, mat: np.ndarray) -> np.ndarray:
        """
        predicts the cluster labels for a provided 2D matrix

        inputs:
            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features

        outputs:
            np.ndarray
                a 1D array with the cluster label for each of the observations in `mat`
        """
        assert self.centroids.size != 0, "Model must be fit before running predict."

        predictions = []

        # Compute Euclidean distance from each point to the model's existing centroids
        dists = cdist(mat, self.centroids, self.metric)

        num_points = mat.shape[0]

        # Iterate through points, assigning each to the closest cluster
        for m in range(num_points):
            m_dists = dists[m].tolist()
            c = m_dists.index(min(m_dists))
            predictions.append(c)

        return np.array(predictions)

    def get_error(self, mat) -> float:
        """
        returns the final mean-squared error of the fit model

        inputs:
            mat: np.ndarray
                A 2D matrix where the rows are observations and columns are features

        outputs:
            float
                the mean-squared error of the fit model
        """
        errors = []

        for i in range(self.k):

            points_indices = np.where(np.asarray(self.labels) == i)[0]

            # If no points are assigned to the cluster, don't add anything to the error
            if len(points_indices) == 0:
                pass

            else:
                points = mat[points_indices]
                cluster_size = points.shape[0]
                centroid = np.reshape(np.sum(points, axis=0)/cluster_size, (1, mat.shape[1]))

                # Calculate the distance from each point to its corresponding centroid.
                dists = cdist(points, centroid, self.metric)

                # Square and store the distances
                errors = errors + [x**2 for x in dists.flatten().tolist()]

        # If points have not been assigned to clusters, return a large error
        if len(errors) == 0:
            return np.inf

        else:
            # Average the squared distances.
            return sum(errors)/len(errors)

    def get_centroids(self) -> np.ndarray:
        """
        returns the centroid locations of the fit model

        outputs:
            np.ndarray
                a `k x m` 2D matrix representing the cluster centroids of the fit model
        """
        return self.centroids


import numpy as np
import pandas as pd
import random
from math import sqrt
from collections import Counter
import warnings # to warn user on using a dumb number for k

def k_nearest_neighbors(data,predict,k=3):
    if(len(data))>=k:
        warnings.warn('K is set a value less than total voting groups')
    distances=[]
    for group in data:
        for features in data[group]:
            # 1. euclidean_distance=sqrt( (features[0]-predict[0])**2 + (features[1]-predict[1])**2 )
            # this is hard coded for only two dimensions, so we'll use numpy instead of the above formula as it is faster
            # 2. euclidean_distance=np.sqrt(np.sum((np.array(features)-np.array(predict))**2))
            # even simpler version than the above one is
            euclidean_distance=np.linalg.norm(np.array(features)-np.array(predict))
            distances.append([euclidean_distance,group]) # add [euclidean_distance, group] to distances
    #for i in sorted(distances[:k]):
    #    i[i]
    #instead
    votes=[i[1] for i in sorted(distances)[:k]]
    votes_result=Counter(votes).most_common(1)[0][0] # as we only care about the one most common vote
    confidence=Counter(votes).most_common(1)[0][1] / k # how many / k

    return votes_result,confidence
accuracies=[]
for i in range(25):
    df=pd.read_csv("breast-cancer-wisconsin-data.csv") # reads the csv in df
    df.replace('?',-9999,inplace=True) # handle errorneous values
    df.drop(['id'],1,inplace=True) # not useful
    full_data=df.astype(float).values.tolist() # every value in the dataset ought to be a float
    random.shuffle(full_data) # just to shuffle the dataset

    # our version of train_test_split
    test_size=0.2
    train_set={2:[],4:[]}
    test_set={2:[],4:[]}
    train_data=full_data[:-int(test_size*len(full_data))]
    test_data=full_data[-int(test_size*len(full_data)):]

    for i in train_data:
        train_set[i[-1]].append(i[:-1])

    for i in test_data:
        test_set[i[-1]].append(i[:-1])

    correct=0
    total=0

    for group in test_set:
        for data in test_set[group]:
            vote,confidence=k_nearest_neighbors(train_set,data,k=5)
            if group==vote: # if the prediction is correct
                correct=correct+1
            # else:
               # print(confidence) # confidence scores for incorrect votes
            total=total+1

    #print('Accuracy: ',correct/total)
accuracies.append(correct/total)
print(sum(accuracies)/len(accuracies))

# HIGHEST ACCURACY OBTAINED = 99.28% in 25 epochs


import numpy as np
from PIL import Image
from matplotlib import pyplot as plt
import seaborn as sns

def get_kernels():
    kernels = []
    kernels.append(('Identity',
                     np.array([[0, 0, 0],
                               [0, 1, 0],
                               [0, 0, 0]])))
    kernels.append(('Edge Detection1',
                     np.array([[1, 0, -1],
                               [0, 0, 0],
                               [-1, 0, 1]])))
    kernels.append(('Edge Detection2',
                     np.array([[0, 1, 0],
                               [1, -4, 1],
                               [0, 1, 0]])))
    kernels.append(('Edge Detection3',
                     np.array([[-1, -1, -1],
                               [-1, 8, -1],
                               [-1, -1, -1]])))
    kernels.append(('Sharpen',
                     np.array([[0, -1, 0],
                               [-1, 5, -1],
                               [0, -1, 0]])))
    kernels.append(('Box Blur',
                     np.array([[1/9, 1/9, 1/9],
                             [1/9, 1/9, 1/9],
                             [1/9, 1/9, 1/9]])))
    kernels.append(('Gaussian Blur',
                     np.array([[1/16, 1/8, 1/16],
                               [1/8, 1/4, 1/8],
                               [1/16, 1/8, 1/16]])))
    return kernels

def pad_img(img, kernel):
    pad_height = (kernel.shape[0] - 1) // 2
    pad_width = (kernel.shape[1] - 1) // 2

    if len(img.shape) == 2:
        return np.pad(img, ((pad_height, pad_height),
                            (pad_width, pad_width)), 'constant')
    return np.pad(img, ((pad_height, pad_height),
                        (pad_width, pad_width),
                        (0, 0)), 'constant')

def convolve2D(img, kernel):
    is_gray_scale = len(img.shape) == 2

    if is_gray_scale:
        img = np.expand_dims(img, axis=2)

    img = pad_img(img, kernel)

    height, width = img.shape[:2]

    new_img = []

    for i in range(height - kernel.shape[0] + 1):
        row = []
        for j in range(width - kernel.shape[1] + 1):
            channels = []
            for k in range(img.shape[2]):
                slice = img[i:i+kernel.shape[0], j:j+kernel.shape[1], k]
                channels.append(np.expand_dims(np.sum(slice * kernel, keepdims=True), axis=0))
            row.append(np.concatenate(channels, axis=2))
        new_img.append(np.concatenate(row, axis=1))

    res = np.maximum(np.concatenate(new_img, axis=0), 0).astype('uint8')

    if is_gray_scale:
        return res[:, :, 0]
    return res

def plot_with_kernels(img):
    kernels = get_kernels()
    n_sub_plots = len(kernels)

    plt.figure('kernels', figsize=(20, 20))

    for i, kernel in enumerate(kernels):
        plt.subplot(n_sub_plots, 3, (i*3) + 1)
        plt.text(0.5, 0.5, kernel[0],
                 horizontalalignment='center',
                 verticalalignment='center',
                 fontsize=15)
        plt.axis('off')

        plt.subplot(n_sub_plots, 3, (i * 3) + 2)
        sns.heatmap(kernel[1], annot=True, cmap='YlGnBu')
        plt.axis('off')

        plt.subplot(n_sub_plots, 3, (i+1) * 3)

        img_ = convolve2D(img, kernel[1])

        if len(img_.shape) == 2:
            plt.imshow(img_, cmap='gray')
        else:
            plt.imshow(img_)

        plt.axis('off')
    plt.show()

def imshow(img):
    plt.imshow(img)
    plt.axis('off')
    plt.show()

def get_activations():
    acts = []
    acts.append(('Sigmoid', lambda x: 1/ (1 + np.exp(-x))))
    acts.append(('Hyperbolic Tangent', lambda x: np.tanh(x)))
    acts.append(('Rectified Linear Unit', lambda x: np.maximum(x, 0)))
    return acts

def plot_activations():
    acts = get_activations()
    x = np.arange(-20, 20, 0.01)

    n_sub_plots = len(acts)
    plt.figure('activations', figsize=(20, 15))
    for i in range(n_sub_plots):
        plt.subplot(n_sub_plots, 3, (i*3) + 1)
        plt.text(0.5, 0.5, acts[i][0],
                 horizontalalignment='center',
                 verticalalignment='center',
                 fontsize=15)
        plt.axis('off')

        plt.subplot(n_sub_plots, 3, (i * 3) + 2)
        eq = np.array(Image.open('images/' + acts[i][0] + '.jpg'))
        plt.imshow(eq)
        plt.axis('off')

        plt.subplot(n_sub_plots, 3, (i+1) * 3)
        plt.plot(x, acts[i][1](x))
        plt.xlabel('x')
        plt.ylabel('y')
    plt.show()


from scipy.optimize import curve_fit
from scipy.interpolate import CubicSpline
import numpy as np
import matplotlib.pyplot as plt
import Bisection

def SHM(t, A, omega, phi, d):
    return A*np.cos(omega*t + phi) + d

t, y = np.loadtxt("SHM-200g.txt", unpack=True)
sig = np.zeros(len(y))
sig.fill(0.001)

A = (y.max() - y.min())/2.0

cs = CubicSpline(t, y)
pairs = Bisection.find_sign_changes(cs, 0.1, t.min(), t.max())
zeros = Bisection.bisection(cs, pairs, 1E-10, 100)

T = 0

for i in range(1, len(zeros)):
    T += zeros[i] - zeros[i-1]
T = 2*T/(len(zeros) - 1)
print(T)

phi = np.arccos(y[0]/A)
omega = 2*np.pi/T
print(A, omega, phi)

theta_0 = [A, omega, phi, 0.0]
theta, cov = curve_fit(SHM, t, y, p0=theta_0, sigma=sig)
print(theta)
print(np.sqrt(np.diag(cov)))
cor = np.zeros_like(cov)

for i in range(0, len(theta)):
    for j in range(0, len(theta)):
        cor[i][j] = cov[i][j]/np.sqrt(cov[i][i]*cov[j][j])

print(cor)

t_m = np.linspace(t.min(), t.max(), 1000)
y_m = SHM(t_m, theta[0], theta[1], theta[2], theta[3])

plt.errorbar(t, y, sig, fmt = ".")
plt.plot(t_m, y_m)
plt.show()


import numpy as np

from modpy.design._design_util import _scale_factorial_design

def full_factorial_design(joint, level=2, scale=False):
    """
    Create a full factorial design sampling 2 ** n values, one for each vertex of the hypercube.

    Parameters
    ----------
    joint : JointDistribution
        Class JointDistribution.
    level : int
        Level of investigation, 2: min & max, 3: min, mode & max
    scale : boolean
        If true, scale design to the support (and possibly reference value) of the distributions

    Returns
    -------
    design : array_like, shape (2 ** n, n)
        Design matrix of the form 'number of runs' x 'number of variables'.
    """

    if level not in (2, 3):
        raise ValueError('`level` must be either 2 or 3.')

    D = ff2n(len(joint.marginals))

    if scale:
        D = _scale_factorial_design(D, joint, level=level)

    return D

def fullfact(levels):
    """
    Create a full factorial design sampling an arbitrary number of levels per factor.

    NOTE: Inspired from https://github.com/tirthajyoti/Design-of-experiment-Python/blob/master/doe_factorial.py

    Parameters
    ----------
    levels : array_like
        Array of levels at which to sample each factor

    Returns
    -------
    D : array_like
        Design matrix
    """

    k = len(levels)  # number of factors
    ne = np.prod(levels)  # number of experiments
    D = np.zeros((ne, k))

    lvl_rep = 1
    ran_rep = ne
    for i in range(k):

        ran_rep //= levels[i]  # integer floor division
        lvl = []

        for j in range(levels[i]):
            lvl += [j] * lvl_rep

        D[:, i] = lvl * ran_rep
        lvl_rep *= levels[i]

    return D

def ff2n(n):
    """
    Create a full factorial design with 2 levels sampling `n` variables.

    Parameters
    ----------
    n : int
        Number of variables in the experiment

    Returns
    -------
    D : array_like
        Design matrix
    """

    return 2 * fullfact([2] * n) - 1


# -*- coding: utf-8 -*-
"""
Created on Mon Nov  6 22:42:14 2017

@author: SunyoolChae
"""

import pandas as pd
import numpy as np

df = pd.read_csv(
    filepath_or_buffer='https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data',
    header=None,
    sep=',')

df.columns = ['sepal_len', 'sepal_wid', 'petal_len', 'petal_wid', 'class']

df.head()

X = df.iloc[:,0:4].values
y = df.iloc[:,4].values

#define kernel matrix
def kernel_matrix(x, kernel=None, d=3, sigma=None, C=1.):

    n = x.shape[0]
    if sigma is None:
        sigma = 1./n

    xxt = x.dot(x.T)
    if kernel == 'polynomial':
        return (C + xxt)**d
    elif kernel == 'sigmoid':
        return np.tanh(sigma*xxt + C)
    elif kernel == 'rbf':
        A = x.dot(x.T)
        B = np.repeat(np.diag(xxt), n).reshape(n, n)
        return np.exp(-(B.T - 2*A + B)/(2*sigma**2))
    else:
        return xxt

K = kernel_matrix(X, kernel='polynomial', sigma=0.2)
print(K.shape)
print(K)

n = K.shape[0]
one_mat = np.repeat(1/n, n**2).reshape(n, n)
gram = K - one_mat.dot(K) - K.dot(one_mat) + one_mat.dot(K).dot(one_mat)

eigen_vals, eigen_vecs = np.linalg.eigh(gram)

print("eigen_values \n{}".format(eigen_vals))
print("eigen_vectors \n{}".format(eigen_vecs))

eigen_pairs = [(eigen_vals[i], eigen_vecs[:,i]) for i in range(len(eigen_vals))]
eigen_pairs.sort(key = lambda x: x[0], reverse=True)

from sklearn.decomposition import KernelPCA
kpca0 = KernelPCA(n_components=3, kernel='poly')
kpca0.fit(X)

kpca0_eigen_vecs = kpca0.alphas_
eigen_vecs_for_comparison = np.vstack([eigen_pairs[0][1], eigen_pairs[1][1], eigen_pairs[2][1]]).T

import matplotlib.pyplot as plt
%matplotlib inline

#first eigenvector
plt.subplot(2,1,1)
plt.plot(kpca0_eigen_vecs[:,0], c='blue')
plt.subplot(2,1,2)
plt.plot(eigen_vecs_for_comparison[:,0], c='green')

#Second eigenvector
plt.subplot(2,1,1)
plt.plot(kpca0_eigen_vecs[:,1], c='blue')
plt.subplot(2,1,2)
plt.plot(eigen_vecs_for_comparison[:,1], c='green')

#Third eigenvector
plt.subplot(2,1,1)
plt.plot(kpca0_eigen_vecs[:,2], c='blue')
plt.subplot(2,1,2)
plt.plot(eigen_vecs_for_comparison[:,2], c='green')

# transform data
n_components = 2

transformed_data = []
for j in range(n):
    loc = np.zeros(n_components)
    for k in range(n_components):
        inner_prod_sum = 0.
        for i in range(n):
            inner_prod_sum += eigen_pairs[k][1][i] * gram[j,i]
        loc[k] = inner_prod_sum/np.sqrt(eigen_pairs[k][0])
    transformed_data.append(loc)

transformed_data = np.array(transformed_data)

label = df['class'].unique()
print(label)

with plt.style.context("seaborn-darkgrid"):
    for l in zip(label):
        plt.scatter(transformed_data[y==l,0], transformed_data[y==l,1],
                    label=l)
    plt.xlabel("PC 1")
    plt.ylabel("PC 2")
    plt.legend()
    plt.show()

kpca0 = KernelPCA(n_components=2, kernel='poly')
Y = kpca0.fit_transform(X)

with plt.style.context("seaborn-darkgrid"):
    for l in label:
        plt.scatter(Y[y==l,0], Y[y==l,1],label=l)
    plt.xlabel("PC 1")
    plt.ylabel("PC 2")
    plt.legend()
    plt.show()

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
Y_ = pca.fit_transform(X)

with plt.style.context("seaborn-darkgrid"):
    for l in label:
        plt.scatter(Y_[y==l,0], Y_[y==l,1],label=l)
    plt.xlabel("PC 1")
    plt.ylabel("PC 2")
    plt.legend()
    plt.show()

02 Kernel-based Learning/Tutorial 07 - Kernel PCA/KPCA_Chae_1.py


import openmesh as om
from math import sin, cos
import numpy as np

def translate(mesh, dx=0, dy=0, dz=0):
	for vh in mesh.vertices():
		mesh.point(vh)[0] += dx
		mesh.point(vh)[1] += dy
		mesh.point(vh)[2] += dz
	return mesh

def escalate(mesh, Sx=1, Sy=1, Sz=1):
	for vh in mesh.vertices():
		mesh.point(vh)[0] *= Sx
		mesh.point(vh)[1] *= Sy
		mesh.point(vh)[2] *= Sz
	return mesh

def getGeometricCenter(mesh):
	maxCoord = np.copy(mesh.point(next(mesh.vertices())))
	minCoord = np.copy(maxCoord)

	for vh in mesh.vertices():
		coord = mesh.point(vh)

		if coord[0] < minCoord[0]: minCoord[0] = coord[0]
		if coord[1] < minCoord[1]: minCoord[1] = coord[1]
		if coord[2] < minCoord[2]: minCoord[2] = coord[2]
		if coord[0] > maxCoord[0]: maxCoord[0] = coord[0]
		if coord[1] > maxCoord[1]: maxCoord[1] = coord[1]
		if coord[2] > maxCoord[2]: maxCoord[2] = coord[2]

	return np.array([
		(maxCoord[0] + minCoord[0])/2,
		(maxCoord[1] + minCoord[1])/2,
		(maxCoord[2] + minCoord[2])/2
		])

def rotX(meshIn, angle):

	GC = getGeometricCenter(meshIn)
	mesh = translate(meshIn, -GC[0], -GC[1], -GC[2])

	for vh in mesh.vertices():
		coord = np.copy(mesh.point(vh))
		mesh.point(vh)[1] = coord[1]*cos(angle) - coord[2]*sin(angle)
		mesh.point(vh)[2] = coord[1]*sin(angle) + coord[2]*cos(angle)
		coord = mesh.point(vh)

	return translate(mesh, GC[0], GC[1], GC[2])

def rotY(meshIn, angle):

	GC = getGeometricCenter(meshIn)
	mesh = translate(meshIn, -GC[0], -GC[1], -GC[2])

	for vh in mesh.vertices():
		coord = np.copy(mesh.point(vh))
		mesh.point(vh)[0] = coord[0]*cos(angle) + coord[2]*sin(angle)
		mesh.point(vh)[2] = coord[2]*cos(angle) - coord[0]*sin(angle)

	return translate(mesh, GC[0], GC[1], GC[2])

def rotZ(meshIn, angle):

	GC = getGeometricCenter(meshIn)
	mesh = translate(meshIn, -GC[0], -GC[1], -GC[2])

	for vh in mesh.vertices():
		coord = np.copy(mesh.point(vh))
		mesh.point(vh)[0] = coord[0]*cos(angle) - coord[1]*sin(angle)
		mesh.point(vh)[1] = coord[0]*sin(angle) + coord[1]*cos(angle)

	return translate(mesh, GC[0], GC[1], GC[2])


# -*- coding: utf-8 -*-
"""
Compute nice borders for a print.

The borders are computed so the area of the mat equals the
area of the window times "Golden Ratio".

They are also computed so that the top, left and right borders
are the same, and the bottom border is bigger to compensate for
the optical illusion that the print "sinks" into the mat.

This also allows to use "Golden Ratio"^2 for bigger borders, and
any multiplicator to that.

It's also possible to compute the print size from a desired mat
size.

:copyright: (c) 2019, Serge Émond
:license: BSD 3-Clause, http://opensource.org/licenses/BSD-3-Clause
"""

import re

import click

from mpmath import mp

mp.dps = 15

# Precision to use when printing dimensions
PRECS = {"mm": 1, "cm": 2, "dm": 3, "m": 4, "in": 2, "ft": 4}
POSSIBLE_UNITS = ["mm", "cm", "dm", "m", "in", "ft"]
_punits = "(?P<u>" + "|".join(POSSIBLE_UNITS) + ")?$"
_dim_re = re.compile(r"(?P<v>[0-9.-]+)" + _punits)
_dim_pair_re = re.compile(r"(?P<w>[0-9.]+)(?P<sep>x|X|,|:|-)(?P<h>[0-9.]+)" + _punits)

# The Golden Ratio
PHI = (1 + mp.sqrt(5)) / 2

def _A(b, x, mat):
    bp = -b if mat else b
    return b * b / (x + bp)

def compute_bottom_border(b, dims, mat=False):
    """Compute the bottom border for a given border size."""
    x, y = dims
    return b + _A(b, x, mat)

def compute_border_sizes(dims, phi, mat=False):
    """Compute optimal border sizes for a mat window mesuring dims[0] x dims[1].

    Returns a tuple (top_left_right_border, bottom_border).

    The computed top, left and right borders have the same value.

    The computed bottom border is bigger to compensate for the
    optical illusion of the picture "sinking" in the frame.

    The values are computed so that the mat's surface is the window's
    surface times `phi`.

    Passing `mat=True` causes the computation to be reversed: the borders
    are computed from a mat size.
    """
    x, y = dims
    if mat:
        # dims are mat's dims
        coeffs = [
            -2 * phi,
            5 * x * phi + 2 * y * phi,
            -2 * x * x * phi - 3 * x * y * phi + x * y,
            x * x * y * (phi - 1),
        ]
    else:
        # dims are window's dims
        coeffs = [
            6,
            7 * x + 2 * y,
            x * ((3 - phi) * y + 2 * x),
            (1 - phi) * mp.power(x, 2) * y,
        ]

    roots = mp.polyroots(coeffs, 50)
    roots = [root for root in roots if mp.im(root) == mp.mpf(0)]

    # If multiple solutions, keep the one with smalest borders first
    roots.sort()
    sols = [
        # (root, root + mp.power(root, 2) / (x + root))
        (root, root + _A(root, x, mat))
        for root in roots
    ]

    # Shouldn't happen
    if len(roots) < 1:
        raise Exception("Error, could not find solution for borders")
    elif len(roots) > 1:
        click.echo("*Warning: multiple solutions. Keeping smallest border.", err=True)

    return sols[0]

def to_mm(dim, units=None, inverse=False):
    """Convert to millimeters."""
    dim = mp.mpf(dim)
    if units == "cm":
        mult = mp.mpf(10)
    elif units == "dm":
        mult = mp.mpf(100)
    elif units == "m":
        mult = mp.mpf(1000)
    elif units == "in":
        mult = mp.mpf("25.4")
    elif units == "ft":
        mult = mp.mpf("304.8")
    else:
        mult = mp.mpf("1")

    if inverse:
        # convert *from* mm
        return dim / mult
    return dim * mult

def parse_dim(dstr, default_units=None):
    """Parse a string dimension to mm."""
    m = _dim_re.match(dstr)
    if not m:
        click.echo("ERROR: Can't parse dimension {!r}".format(dstr), err=True)
        return None

    units = m.group("u")
    dim = m.group("v")

    if not units:
        units = default_units

    return to_mm(dim, units)

def parse_dim_pair(dstr, default_units=None, allow_single=False):
    """Parse a string representing a pair of dimensions, to mm."""
    m = _dim_pair_re.match(dstr)
    if not m:
        v = parse_dim(dstr)
        return v, v
        click.echo("ERROR: Can't parse dimension pair {!r}".format(dstr), err=True)
        return None

    units = m.group("u")
    width = m.group("w")
    height = m.group("h")

    if not units:
        units = default_units

    return to_mm(width, units), to_mm(height, units)

def dim_str(dim, units):
    """Format a dimension to a given unit."""
    dim = to_mm(dim, units, True)
    prefix = ""
    if units == "ft":
        if dim >= mp.mpf(1):
            prefix = str(int(mp.floor(dim))) + "'"
        units = "in"
        dim = mp.frac(dim) * 12
    dim = prefix + ("{:." + str(PRECS[units]) + "f}").format(float(mp.nstr(dim)))
    if units == "in":
        return dim + '"'

    return dim + " " + units

def dim_pair_str(dims, units):
    """Generate a dimension pair string."""
    return "{} x {}".format(dim_str(dims[0], units), dim_str(dims[1], units))

@click.command()
@click.argument("print-dims")
@click.option("--units", default="mm", help="Default unit, also used for output")
@click.option(
    "--mat/--print", default=False, help="PRINT_DIMS are mat/print size, default print"
)
@click.option(
    "--paper",
    default="0x0",
    help="Dims of the paper of the print, assumed to be centered",
)
@click.option(
    "--overlap",
    default="0",
    help="Overlap of mat's window over the print (pair or single)",
)
@click.option(
    "--factor", default=None, help="Factor to use (mat area = factor * window area)"
)
@click.option("--exp", default=None, help="Exponent to use for the factor")
@click.option(
    "--border", default=None, help="Use fixed border size of this value, compute bottom"
)
def main(print_dims, overlap, units, paper, factor, exp, mat, border=None):
    """Compute mat/window size so my prints look good.

    Given a print's size, compute the required mat dimensions.
    Given a mat's size, compute the required print dimensions.

    \b
    The final border is computed so that:
        - the bottom border is bigger, to compensate for the illusion
          of the print "sinking in" the border
        - the other three borders are equal
        - the mat's area is a factor of the print's size
          (e.g.  mat surface = factor^exp * window size)

    \b
    The default factor is the (Golden Ratio)^(3/2).
        (e.g. --factor=golden, --exp=1.5)

    The border can also have a fixed dimension, in which case only the
    bottom border is still computed.

    An *overlap* can be given, representing the mat's window overlapping
    the print to make sure the print is completely covered.

    This overlap may be a single dimension, or given as a dimension pair.

    \b
    A single dimension is given as <number><optional unit>.
    A dimension pair is given as <width><sep><height><optional unit>.

    `sep` may be 'x', ':', ','.

    The optional unit may be `mm`, `cm`, `dm`, `in`, `ft`.

    When not specified, the unit is the default unit (parameter --unit).
    The default unit is `mm`.

    A paper size can also be given, which allows for the computation of
    the position of the paper on the mat.

    \b
    +--------------------------------------+  ----------------
    |             Border top (b)           |                ^
    |   +------------------------------+   |  ----------    |
    |   |              Oh              |   |          ^     |
    |   |  +------------------------+  |   |  ---     |     |
    |   |  |                        |  |   |   ^      | P   |
    |   |Ow|         Window         |Ow|   |   |Win-  | r   | M
    |   |  |                        |  |   |   |dow   | i   | a
    | b |  |                        |  | b |   v      | n   | t
    |   |  +------------------------+  |   |  ---     | t   |
    |   |              Oh              |   |          v     |
    |   +------------------------------+   |  ----------    |
    |                                      |                |
    |            Border bottom             |                v
    +--------------------------------------+  ----------------

    \b
    Ow = overlap in width
    Oh = overlap in height
    If Ow = Oh = 0, the window's size equals the print's size

    """
    default_units = units if units in POSSIBLE_UNITS else "mm"
    if units and units not in POSSIBLE_UNITS:
        click.echo(
            "ERROR: Unknown units, {!r}, defaulting to mm".format(units), err=True
        )

    overlap_dims = parse_dim_pair(
        overlap, default_units=default_units, allow_single=True
    )
    print_dims = parse_dim_pair(print_dims, default_units=default_units)
    paper_dims = parse_dim_pair(paper, default_units=default_units)
    if not factor or factor in ("phi", "gold", "golden", "golden-ratio"):
        factor = PHI
        if not exp:
            exp = "1.5"
    else:
        factor = mp.mpf(factor)
    if exp:
        factor = mp.power(factor, mp.mpf(exp))

    if border is not None:
        border_dim = parse_dim(border, default_units=default_units)
    else:
        border_dim = None

    if mat:
        # print is mat, compute window and print sizes
        mat_dims = print_dims

        if border_dim:
            b, b_bottom = (
                border_dim,
                compute_bottom_border(border_dim, mat_dims, mat=True),
            )
        else:
            b, b_bottom = compute_border_sizes(mat_dims, factor, mat=True)

        window_dims = (mat_dims[0] - 2 * b, mat_dims[1] - b - b_bottom)

        print_dims = [dim[0] + dim[1] * 2 for dim in zip(window_dims, overlap_dims)]

    else:
        # print is print, compute mat and window sizes
        window_dims = [dim[0] - dim[1] * 2 for dim in zip(print_dims, overlap_dims)]

        if border_dim:
            b, b_bottom = (
                border_dim,
                compute_bottom_border(border_dim, window_dims, mat=False),
            )
        else:
            b, b_bottom = compute_border_sizes(window_dims, factor, mat=False)

        mat_dims = (window_dims[0] + 2 * b, window_dims[1] + b + b_bottom)

    if paper_dims[0] < print_dims[0] or paper_dims[1] < print_dims[1]:
        paper_dims = [dim for dim in print_dims]

    paper_borders = [(dim[0] - dim[1]) / 2 for dim in zip(paper_dims, print_dims)]

    paper_shift = [b - dim[1] - dim[0] for dim in zip(paper_borders, overlap_dims)]

    print("== Parameters ============================================")
    print(
        "Print size        {print}\n"
        "Paper size        {paper}\n"
        "Window overlap    {overlap}\n"
        "Mat window size * {win}".format(
            print=dim_pair_str(print_dims, units),
            paper=dim_pair_str(paper_dims, units),
            overlap=dim_pair_str(overlap_dims, units),
            win=dim_pair_str(window_dims, units),
        )
    )

    print("\n== Mat Information ========================================")
    print(
        "Mat size        * {mat}\n"
        "Window position\n"
        "  Top           * {top}\n"
        "  Bottom        * {bottom}\n"
        "  Left / Right  * {lr}".format(
            mat=dim_pair_str(mat_dims, units),
            top=dim_str(b, units),
            bottom=dim_str(b_bottom, units),
            lr=dim_str(b, units),
        )
    )

    print("\n== Paper position from sides of mat =======================")
    print(
        "  Top           * {top}\n"
        "  Bottom        * {bottom}\n"
        "  Left          * {left}\n"
        "  Right         * {right}".format(
            top=dim_str(paper_shift[1], units),
            bottom=dim_str(mat_dims[1] - paper_dims[1] - paper_shift[1], units),
            left=dim_str(paper_shift[0], units),
            right=dim_str(mat_dims[0] - paper_dims[0] - paper_shift[0], units),
        )
    )

if __name__ == "__main__":
    main()


import math
import numpy as np

def sample_mask_with_rate(shape, one_rate):
    assert len(shape) == 2, "'shape' must be a list/tuple of format (batch, dim)!"
    assert 0.0 < one_rate < 1.0, "'one_rate' must be in (0, 1)"

    num_ones = int(round(one_rate * shape[1], 0))
    num_zeros = shape[1] - num_ones

    if num_ones == 0:
        return np.zeros(shape, dtype=np.bool)
    elif num_zeros == 0:
        return np.ones(shape, dtype=np.bool)
    else:
        mask = np.concatenate([np.ones([shape[0], num_ones], dtype=np.bool),
                               np.zeros([shape[0], num_zeros], dtype=np.bool)], axis=1)
        mask = np.apply_along_axis(np.random.permutation, axis=1, arr=mask)
        return mask

def normal_density(x, mean, stddev, from_axis=None, eps=1e-8):
    variance = np.maximum(stddev ** 2, eps)
    stddev = np.maximum(stddev, eps)

    density = np.exp(-np.square(x - mean) / (2 * variance)) / (stddev * math.sqrt(2 * math.pi))

    if (from_axis is not None) and (from_axis >= 0):
        shape = tuple(density.shape[:from_axis]) + (np.prod(density.shape[from_axis:]),)
        density = np.reshape(density, shape)
        density = np.prod(density, axis=from_axis)

    return density

def normal_density_cupy(x, mean, stddev, from_axis=None, eps=1e-8, gpu=0):
    import cupy as cp

    with cp.cuda.Device(gpu):
        variance = cp.maximum(stddev ** 2, eps)
        stddev = cp.maximum(stddev, eps)

        density = cp.exp(-cp.square(x - mean) / (2 * variance)) / (stddev * math.sqrt(2 * math.pi))

        if (from_axis is not None) and (from_axis >= 0):
            shape = tuple(density.shape[:from_axis]) + (cp.prod(density.shape[from_axis:]),)
            density = cp.reshape(density, shape)
            density = cp.prod(density, axis=from_axis)

        return density

def normal_log_density(x, mean, stddev, from_axis=None, eps=1e-8):
    variance = np.maximum(stddev ** 2, eps)
    log_stddev = np.log(np.maximum(stddev, eps))

    log_density = -0.5 * (math.log(2 * math.pi) + 2 * log_stddev + ((x - mean) ** 2 / variance))

    if (from_axis is not None) and (from_axis >= 0):
        shape = tuple(log_density.shape[:from_axis]) + (np.prod(log_density.shape[from_axis:]),)
        log_density = np.reshape(log_density, shape)
        log_density = np.sum(log_density, axis=from_axis)

    return log_density

def normal_log_density_cupy(x, mean, stddev, from_axis=None, eps=1e-8, gpu=0):
    import cupy as cp

    with cp.cuda.Device(gpu):
        variance = cp.maximum(stddev ** 2, eps)
        log_stddev = cp.log(cp.maximum(stddev, eps))

        log_density = -0.5 * (math.log(2 * math.pi) + 2 * log_stddev + ((x - mean) ** 2 / variance))

        if (from_axis is not None) and (from_axis >= 0):
            shape = tuple(log_density.shape[:from_axis]) + (cp.prod(log_density.shape[from_axis:]),)
            log_density = cp.reshape(log_density, shape)
            log_density = cp.sum(log_density, axis=from_axis)

        return log_density

def at_bin(x, bin_edges, one_hot=True):
    # x: (batch, )
    # bin_edges: num_bins + 1

    x = np.asarray(x)
    bin_edges = np.asarray(bin_edges)

    assert len(x.shape) == 1 and len(bin_edges.shape) == 1, \
        "x.shape: {}, bin_edges.shape: {}".format(x.shape, bin_edges.shape)

    low_edges = bin_edges[:-1]  # num_bins
    high_edges = bin_edges[1:]  # num_bins

    # (batch, num_bins)
    x_at_bin = np.logical_and(np.greater_equal(np.expand_dims(x, axis=1), np.expand_dims(low_edges, axis=0)),
                              np.less(np.expand_dims(x, axis=1), np.expand_dims(high_edges, axis=0)))

    min_edge = bin_edges[0]
    max_edge = bin_edges[-1]

    for n in range(len(x_at_bin)):
        if x[n] < min_edge:
            assert np.sum(x_at_bin[n].astype(np.int32)) == 0
            x_at_bin[n, 0] = True

        if x[n] >= max_edge:
            assert np.sum(x_at_bin[n].astype(np.int32)) == 0, x_at_bin[n]
            x_at_bin[n, -1] = True

    if not one_hot:
        x_at_bin = np.argmax(x_at_bin, axis=-1).astype(np.int32)

    # (batch, num_bins)
    return x_at_bin

def logsumexp(value, axis=None, keepdims=False, gpu_support=None):
    if gpu_support == "jax":
        print("Use jax to compute 'normal_log_density'!")
        import jax.numpy as np
    elif gpu_support == "cupy":
        print("Use cupy to compute 'normal_log_density'!")
        import cupy as np
    else:
        import numpy as np

    if axis is not None:
        max_val = np.max(value, axis=axis, keepdims=True)
        value0 = value - max_val
        if not keepdims:
            max_val = np.squeeze(max_val, axis)
        return max_val + np.log(np.sum(np.exp(value0), axis=axis, keepdims=keepdims))
    else:
        max_val = np.max(value)
        sum_exp = np.sum(np.exp(value - max_val))
        return max_val + math.log(sum_exp)

def logsumexp_cupy(value, axis=None, keepdims=False, gpu=0):
    import cupy as cp
    with cp.cuda.Device(gpu):
        if axis is not None:
            max_val = cp.max(value, axis=axis, keepdims=True)
            value0 = value - max_val
            if not keepdims:
                max_val = cp.squeeze(max_val, axis)
            return max_val + cp.log(cp.sum(cp.exp(value0), axis=axis, keepdims=keepdims))
        else:
            max_val = cp.max(value)
            sum_exp = cp.sum(cp.exp(value - max_val))
            return max_val + math.log(sum_exp)

def variance_uniform(low, high):
    # Variance of uniform distribution in the range [low, high]
    return (high - low) ** 2 / 12.0

def variance_categorical(prob):
    # Variance of categorical distribution with K classes, each has probability p1, p2,..., pK
    # prob: (batch, K)
    # var: (batch, K)

    # assert np.sum(prob) == 1.0, "'prob' must sum to 1. sum(prob) = {}!".format(np.sum(prob))
    # If prob = [0.6, 0.1, 0.3], variance (for each class) is = [0.6 * 0.4, 0.1 * 0.9, 0.3 * 0.7]
    return prob * (1.0 - prob)


from dataclasses import dataclass, field
from typing import Tuple

import matplotlib.pyplot as plt
import numpy as np
from numpy.typing import NDArray

from .. import RecordBase, to_numpy_array

@dataclass
class PCA:

    autoscaled_data: Tuple[RecordBase]
    np_data: NDArray[np.float64] = field(init=False)
    correlation_matrix: NDArray[np.float64] = field(init=False)
    sorted_eigenval_vector_pairs: Tuple[float, NDArray[np.float64]] = field(
        init=False
    )

    def __post_init__(self):
        self.np_data = to_numpy_array(self.autoscaled_data)
        self.correlation_matrix = (self.np_data.T @ self.np_data) / (
            self.np_data.shape[0]
        )

    def execute(self):
        eigenvalue = np.linalg.eig(self.correlation_matrix)
        self.sorted_eigenval_vector_pairs = sorted(
            zip(*eigenvalue), key=lambda e: e[0], reverse=True
        )

    @property
    def lambdas(self):
        return [l for l, _ in self.sorted_eigenval_vector_pairs]

    def scree_plot(self):
        x = np.arange(1, len(self.all_eigenvalue_vector_pairs()) + 1)
        plt.plot(x, self.lambdas)
        plt.scatter(x, self.lambdas)
        plt.title(
            f"Scree Plot for PCA on {len(self.autoscaled_data)} {self.autoscaled_data[0].class_name()} objects"
        )
        plt.ylabel("λ (eigenvalue)")
        plt.xlabel("Principal components")
        plt.xticks(x, [f"PC{i}" for i in x])
        plt.grid(True)

    def percent_scree_plot(self):
        x = np.arange(1, len(self.all_eigenvalue_vector_pairs()) + 1)
        m = len(self.autoscaled_data[0].numeric())
        prct_lambdas = [v / m for v in self.lambdas]
        label = "% of variance explained"
        plt.plot(x, prct_lambdas)
        plt.scatter(x, prct_lambdas)
        plt.title(
            f"Scree Plot for PCA on {len(self.autoscaled_data)} {self.autoscaled_data[0].class_name()} objects"
        )
        plt.ylabel(label)
        y = np.linspace(
            min(prct_lambdas), max(prct_lambdas), len(prct_lambdas)
        )
        plt.yticks(y, [f"{f*100:.1f}%" for f in y])
        plt.xlabel("Principal components")
        plt.xticks(x, [f"PC{i}" for i in x])
        plt.grid(True)

    def cumulative_percent_scree_plot(self):
        x = np.arange(0, len(self.all_eigenvalue_vector_pairs()) + 1)
        m = len(self.autoscaled_data[0].numeric())
        prct_lambdas = [0.0]
        total = 0
        for v in self.lambdas:
            total += v / m
            prct_lambdas.append(total)
        label = "Cumulative % of variance explained"

        plt.plot(x, prct_lambdas)
        plt.scatter(x, prct_lambdas)
        plt.title(
            f"Scree Plot for PCA on {len(self.autoscaled_data)} {self.autoscaled_data[0].class_name()} objects"
        )
        plt.ylabel(label)
        y = np.linspace(
            min(prct_lambdas), max(prct_lambdas), len(prct_lambdas)
        )
        plt.yticks(y, [f"{f*100:.1f}%" for f in y])
        plt.xlabel("Principal components")
        plt.xticks(x, [""] + [f"PC{i}" for i in x[1:]])
        plt.grid(True)

    def all_eigenvalue_vector_pairs(self):
        return self.sorted_eigenval_vector_pairs

    def from_kaiser_criteria(self, treshold: float = 0.95):
        return filter(
            lambda o: o[0] > treshold, self.all_eigenvalue_vector_pairs()
        )


from matplotlib.pyplot import plot
from matplotlib.pyplot import show
from matplotlib.pyplot import grid
from matplotlib.pyplot import xlabel, ylabel, title, xlim

from scipy import signal
from numpy.fft import fft
import numpy as np

from numpy import ones
from numpy import zeros

def single_side_ampl_spectrum( S, Fs ):
    Fs *= 1.0
    L = len( S )             # Length of signal

    Y = fft(S)

    P2 = np.abs(Y/L);
    P1 = P2[ (1-1):(L/2+1-1) ]
    end = 0
    P1[ 2-1:end-1-1 ] = 2*P1[2-1:end-1-1]

    f = Fs*np.arange( 0, (L/2) )/L

    return f, P1

# http://www.mathworks.com/help/matlab/ref/fft.html
def test_single_side_ampl_spectrum():
    Fs = 1000.0;            # Sampling frequency
    T = 1/Fs;             # Sampling period
    L = 1000;             # Length of signal
    t = np.arange(0, L) * T;        # Time vector
    S = 0.7*np.sin(2*np.pi*50*t) + np.sin(2*np.pi*120*t)
    X = S

    #plot(1000*t[1-1:50-1],X[1-1:50-1])
    #title('Signal Corrupted with Zero-Mean Random Noise')
    #xlabel('t (milliseconds)')
    #ylabel('X(t)')

    Y = fft(S);
    P2 = abs(Y/L);
    P1 = P2[ (1-1):(L/2+1-1) ]
    end = 0
    P1[ 2-1:end-1-1 ] = 2*P1[2-1:end-1-1]

    f = Fs*np.arange( 0, (L/2) )/L
    plot(f,P1)
    title('Single-Sided Amplitude Spectrum of S(t)')
    xlabel('f (Hz)')
    ylabel('|P1(f)|')

    grid()
    show()

def freqz_plot( b, a, fs ):
    def f(ar, name):
        def aa(npv):
            if npv < 0:
                return str(npv)
            else:
                return " " + str(npv)
        return name + " = [\n    "+",\n    ".join(map(aa, ar)) + "\n]"

    print f(b, "b")
    print f(a, "a")

    n = 1024*4
    w, h = signal.freqz(b, a, n)  # w = [0, pi]
    f = w / np.pi * 0.5 * fs

    # Ampl
    plot(f, 20 * np.log10(10*abs(h)), 'b')

    # Ph
    angles = np.unwrap(np.angle(h)) * 180 / np.pi

    plot(f, angles, 'g')
    title('Digital filter frequency response')
    xlabel('freq, (Hz)')
    ylabel('|H(s)|, dB')
    xlim([0, max( f )])

    return b, a


import math
import numpy

def kvalue2(nx, ny, dx, dy):
    # [kx,ky] = kvalue2 (nx,ny,dx,dy);
    #   Wavenumber coordinates for 2D FFT Spectrum
    # ASSUMES EVEN NUMBERED NX

    # First, need to get the frequency scale right.
    # Remember, for even number of points in spectrum, central frequency
    # is not right at the centre, hence frequency spacing is given as follows
    kxmax = ((2 * math.pi) / (dx * 2))
    kymax = ((2 * math.pi) / (dy * 2))
    kxinc = kxmax / ((nx / 2))
    kyinc = kymax / ((ny / 2))

    # make grid of correctly scaled frequencies
    kx, ky = numpy.meshgrid(numpy.arange(-kxmax, kxmax, kxinc), numpy.arange(-kymax, kymax, kyinc))

    return kx, ky


import numpy as np

from ..constants import Constants as c

__all__ = ["calcHerrickGibbs"]

MU = c.G * c.M_SUN

def calcHerrickGibbs(r1, r2, r3, t1, t2, t3):
    """
    Calculates the velocity vector at the location of the second position vector (r2) using the
    Herrick-Gibbs formula.

    .. math::
        \vec{v}_2 =
            -\Delta t_{32} \left ( \frac{1}{ \Delta t_{21} \Delta t_{31}}
                + \frac{\mu}{12 r_1^3}  \right ) \vec{r}_2
            + ( \Delta t_{32} - \Delta t_{21}) \left ( \frac{1}{ \Delta t_{21} \Delta t_{32}}
                + \frac{\mu}{12 r_2^3}  \right ) \vec{r}_2
            + \Delta t_{21} \left ( \frac{1}{ \Delta t_{32} \Delta t_{31}}
                + \frac{\mu}{12 r_3^3}  \right ) \vec{r}_3

    For more details on theory see Chapter 4 in David A. Vallado's "Fundamentals of Astrodynamics
    and Applications".

    Parameters
    ----------
    r1 : `~numpy.ndarray` (3)
        Heliocentric position vector at time 1 in cartesian coordinates in units
        of AU.
    r2 : `~numpy.ndarray` (3)
        Heliocentric position vector at time 2 in cartesian coordinates in units
        of AU.
    r3 : `~numpy.ndarray` (3)
        Heliocentric position vector at time 3 in cartesian coordinates in units
        of AU.
    t1 : float
        Time at r1. Units of MJD or JD work or any decimal time format (one day is 1.00) as
        long as all times are given in the same format.
    t2 : float
        Time at r2. Units of MJD or JD work or any decimal time format (one day is 1.00) as
        long as all times are given in the same format.
    t3 : float
        Time at r3. Units of MJD or JD work or any decimal time format (one day is 1.00) as
        long as all times are given in the same format.

    Returns
    -------
    v2 : `~numpy.ndarray` (3)
        Velocity of object at position r2 at time t2 in units of AU per day.
    """
    t31 = t3 - t1
    t32 = t3 - t2
    t21 = t2 - t1

    v2 = (-t32 * (1 / (t21 * t31) + MU / (12 * np.linalg.norm(r1)**3)) * r1
          + (t32 - t21) * (1 / (t21 * t32) + MU / (12 * np.linalg.norm(r2)**3)) * r2
          + t21 * (1 / (t32 * t31) + MU / (12 * np.linalg.norm(r3)**3)) * r3)
    return v2


import numpy as np
import numpy.linalg as la
import timeit
import unittest
from sklearn.linear_model import LinearRegression
import sys
from assignment4 import generate_data
import imp

def check_load(filename,fun_name):
    try:
        user_module = imp.load_source("module",filename)
        if hasattr(user_module,fun_name) and callable(user_module.__dict__[fun_name]):
            print("%s was succesfully loaded"%fun_name)
            return user_module.__dict__[fun_name]
        else:
            print("%s is not defined"%fun_name)
            return None
    except:
        print("Unable to load %s from %s"%(fun_name,filename))
        return None

class TestGaussJordan(unittest.TestCase):
    fn = "assignment4.py"

    def test(self):
        print("*********** Testing gauss_jordan ***********")
        gauss_jordan = check_load(self.fn,"gauss_jordan")
        self._test1(gauss_jordan,"test 1")
        self._test2(gauss_jordan,"test 2")
        self._test_non_invertable(gauss_jordan,"test non-invertible")

    def _test1(self,gauss_jordan,test_name):
        """
            The first example from the slides.
        """
        print("---- %s ----"%test_name)
        A = np.array([[1,3],[2,5]])
        A_inv = np.array([[-5,3],[2,-1]])
        got = gauss_jordan(A)
        print("input:")
        print(A)
        print("expected:")
        print(A_inv)
        print("got:")
        print(got)

        if np.all(np.isclose(got,A_inv)):
            print("result: Correct")
        else:
            print("result: Incorrect!")

    def _test2(self,gauss_jordan,test_name):
        """
            The second example from the slides.
        """
        print("---- %s ----"%test_name)
        A = np.array([[2,3,0],[1,-2,-1],[2,0,-1]])
        A_inv = np.array([[2,3,-3],[-1,-2,2],[4,6,-7]])
        got = gauss_jordan(A)
        print("input:")
        print(A)
        print("expected:")
        print(A_inv)
        print("got:")
        print(got)

        if np.all(np.isclose(got,A_inv)):
            print("result: Correct")
        else:
            print("result: Incorrect!")

    def _test_non_invertable(self,gauss_jordan,test_name):
        """
            A non-invertible example.
        """
        print("---- %s ----"%test_name)
        A = np.array([[2,3,0],[1,-2,-1],[2,-4,-2]])
        got = gauss_jordan(A)
        print("input:")
        print(A)
        print("expected:")
        print(None)
        print("got:")
        print(got)

        if got is None:
            print("result: Correct")
        else:
            print("result: Incorrect!")

class TestLinearRegression(unittest.TestCase):
    fn = "assignment4.py"

    def test(self):
        print("*********** Testing linear regression ***********")
        self._test_linear_regression_inverse()
        self._test_linear_regression_moore_penrose()

    def _test_linear_regression_inverse(self):
        # load function
        print("---- test linear_regression_inverse ----")
        linear_regression_inverse = check_load(self.fn,"linear_regression_inverse")

        # generate data
        n = 10
        m = 5
        X,y = generate_data(n,m)

        # Get correct answer from reference implementation
        lr = LinearRegression(fit_intercept = False)
        lr.fit(X,y)
        beta = lr.coef_

        # Run linear_regression_inverse
        got = linear_regression_inverse(X,y)

        if np.all(np.isclose(got,beta)):
            print("result: Correct")
        else:
            print("result: Incorrect")

    def _test_linear_regression_moore_penrose(self):
        # load function
        print("---- test linear_regression_moore_penrose ----")
        linear_regression_moore_penrose = check_load(self.fn,"linear_regression_moore_penrose")

        # generate data
        n = 10
        m = 5
        X,y = generate_data(n,m)

        # Get correct answer from reference implementation
        lr = LinearRegression(fit_intercept = False)
        lr.fit(X,y)
        beta = lr.coef_

        # Run linear_regression_moore_penrose
        got = linear_regression_moore_penrose(X,y)

        if np.all(np.isclose(got,beta)):
            print("result: Correct")
        else:
            print("result: Incorrect")

if __name__=="__main__":
    if len(sys.argv) > 1:
        fn = sys.argv.pop()
        TestGaussJordan.fn = fn
        TestLinearRegression.fn = fn

    unittest.main()


"""Simulates a 2 Poisson Process Distribution."""

import numpy as np
import pandas as pd
import bokeh_catplot
import bokeh.io

rg = np.random.default_rng()

def generate_samples_2pp(beta1, beta2, n_samples=1):
    """
    Generates n_samples samples according to the 2 Poisson Process Distribution.
    Parameters:
    beta1: Rate of the first event.
    beta2: Rate of the second event.
    n_samples: The number of samples to generate.
    """
    times = []
    for i in range(n_samples):
        time = rg.exponential(1 / beta1) + rg.exponential(1 / beta2)
        times.append(time)
    return times

def generate_samples_gamma(a, b, n_samples=1):
    """
    Draws samples from a gamma distribution

    Parameters:
    a: The alpha term.
    b: The beta term (rate).
    size: The number of samples to draw
    """
    times = np.empty(n_samples)
    for i in range(n_samples):
        times[i] = rg.gamma(a, scale=1/b)
    return times

def create_generated_ecdfs(beta_1, beta_2, n_samples=150):
    """
    Creates a plot containing all generated ECDFs from the 2 Poisson Process
    Distribution. It holds beta 1 constant.
    Parameters:
    beta1: An integer representing beta 1.
    beta2: A list of integers representing beta 2.
    n_samples: The number of samples to generate each time.
    """

    times = {'times': [], 'beta 2': []}

    for val in beta_2:
        sampled_times = generate_samples_2pp(beta_1, val, n_samples=n_samples)
        beta_2_array = [val] * n_samples

        times['times'] += sampled_times
        times['beta 2'] += beta_2_array

    df = pd.DataFrame(data=times)
    df.head()

    p = bokeh_catplot.ecdf(
        data=df,
        cats=['beta 2'],
        val='times',
        style='staircase',
        title='Generated ECDFs for Differing Values of β'
    )

    p.legend.location = 'bottom_right'

    return p

def cdf_points(t, beta_1, beta_2):
    '''
    Gets the points for a given analytical CDF.
    Parameters:
    t: An array of t-values,
    beta_1: A beta 1 value
    beta_2: A beta 2 value.
    It returns an array of the CDF values.
    '''
    ratio = (beta_1 * beta_2) / (beta_2 - beta_1)

    term1 = (1 / beta_1) * (1 - np.exp(-beta_1 * t))
    term2 = (1 / beta_2) * (1 - np.exp(-beta_2 * t))

    return ratio * (term1 - term2)

def theo_empirical_cdf(beta_1, beta_2, n_samples=150):
    """
    Overlays empirical and theoretical ECDFs for the 2 Poisson Process Distribution.
    Parameters:
    beta_1: The beta 1 value
    beta_2: The beta 2 value.
    n_samples: The number of samples to generate.
    """
    # Gets our analytical ECDF
    times = np.arange(0, 20, .1)
    prob_times = cdf_points(times, beta_1, beta_2)

    t = {'times': [], 'beta 2': []}

    sampled_times = generate_samples_2pp(beta_1, beta_2, n_samples=n_samples)
    beta_2_array = [beta_2] * n_samples

    t['times'] += sampled_times
    t['beta 2'] += beta_2_array

    df = pd.DataFrame(data=t)
    df['label'] = 'Empirical CDF'

    p = bokeh_catplot.ecdf(
        data=df,
        cats='label',
        val='times',
        style='staircase',
        show_legend=True,
        conf_int=True,
        title='Empirical and Theoretical CDFs'
    )
    p.circle(
        x=times,
        y=prob_times,
        size=2,
        color='orange',
        legend='Theoretical CDF',
    )

    p.yaxis.axis_label = 'CDF value'

    return p