import os
from itertools import combinations_with_replacement, product
import threading

import numpy as np
from scipy.spatial import distance
from scipy.stats import entropy
from scipy.stats import pearsonr
from sklearn.model_selection import KFold

from FeatureExtractionMode.utils.utils_write import FormatWrite
from MachineLearningAlgorithm.utils.utils_read import files2vectors_seq

SEED = 42

# 欧氏距离
def euclidean_distance(vec1, vec2):
    # ord=2: 二范数
    score = np.linalg.norm(vec1-vec2, ord=2)
    return round(score, 4)

# 曼哈顿距离
def manhattan_distance(vec1, vec2):
    # ord=1: 一范数
    score = np.linalg.norm(vec1 - vec2, ord=1)
    return round(score, 4)

# 切比雪夫距离
def chebyshev_distance(vec1, vec2):
    # ord=np.inf: 无穷范数
    score = np.linalg.norm(vec1-vec2, ord=np.inf)
    return round(score, 4)

# 汉明距离
def hamming_distance(vec1, vec2):
    # 适用于二进制编码格式 !
    return len(np.nonzero(vec1-vec2)[0])  # 返回整数

# 杰卡德相似度
def jaccard_similarity_coefficient(vec1, vec2):
    # 适用于二进制编码格式 !
    score = distance.pdist(np.array([vec1, vec2]), "jaccard")[0]
    return round(score, 4)

# 余弦相似度
def cosine_similarity(vec1, vec2):
    score = np.dot(vec1, vec2)/(np.linalg.norm(vec1)*(np.linalg.norm(vec2)))
    return round(score, 4)

# 皮尔森相关系数
def pearson_correlation_coefficient(vec1, vec2):
    score = pearsonr(vec1, vec2)[0]
    return round(score, 4)

# 相对熵又称交叉熵, Kullback-Leible散度（即KL散度）等,
# 这个指标不能用作距离衡量，因为该指标不具有对称性, 为了在并行计算时统一，采用对称KL散度
def kl_divergence(vec1, vec2):
    score = (entropy(vec1, vec2) + entropy(vec2, vec1)) / 2.0
    return round(score, 4)

def score_func_one(method, vectors1, vectors2, index1, index2, return_dict, sem):
    sem.acquire()
    if method == 'ED':
        score = euclidean_distance(vectors1[index1], vectors2[index2])
    elif method == 'MD':
        score = manhattan_distance(vectors1[index1], vectors2[index2])
    elif method == 'CD':
        score = chebyshev_distance(vectors1[index1], vectors2[index2])
    elif method == 'HD':
        score = hamming_distance(vectors1[index1], vectors2[index2])
    elif method == 'JSC':
        score = jaccard_similarity_coefficient(vectors1[index1], vectors2[index2])
    elif method == 'CS':
        score = cosine_similarity(vectors1[index1], vectors2[index2])
    elif method == 'PCC':
        score = pearson_correlation_coefficient(vectors1[index1], vectors2[index2])
    elif method == 'KLD':
        score = kl_divergence(vectors1[index1], vectors2[index2])
    else:
        print('Semantic Similarity method error!')
        return False
    return_dict[(index1, index2)] = score
    sem.release()

def score4train_vec(method, train_vectors, process_num):
    threads = []
    sem = threading.Semaphore(process_num)
    return_dict = {}
    row = train_vectors.shape[0]

    for i, j in combinations_with_replacement(list(range(len(train_vectors))), 2):
        threads.append(threading.Thread(target=score_func_one,
                                        args=(method, train_vectors, train_vectors, i, j, return_dict, sem)))
    for t in threads:
        t.start()

    for t in threads:
        t.join()

    score_mat = np.zeros([row, row])
    for i in range(row):
        for j in range(row):
            if i <= j:
                score_mat[i][j] = return_dict[(i, j)]
            else:
                score_mat[i][j] = return_dict[(j, i)]
    return score_mat

def score4test_vec(method, train_vectors, test_vectors, process_num):
    threads = []
    sem = threading.Semaphore(process_num)

    return_dict = {}

    train_row = train_vectors.shape[0]
    test_row = test_vectors.shape[0]

    for i, j in product(list(range(test_row)), list(range(train_row))):
        threads.append(threading.Thread(target=score_func_one,
                                        args=(method, test_vectors, train_vectors, i, j, return_dict, sem)))
    for t in threads:
        t.start()

    for t in threads:
        t.join()

    score_mat = np.zeros([test_row, train_row])
    for i in range(test_row):
        for j in range(train_row):
            score_mat[i][j] = return_dict[(i, j)]
    return score_mat

def partition_vectors(vectors, folds_num):
    fold = KFold(folds_num, shuffle=True, random_state=np.random.RandomState(SEED))
    folds_temp = list(fold.split(vectors))

    folds = []
    for i in range(folds_num):
        test_index = folds_temp[i][1]
        train_index = folds_temp[i][0]

        folds.append((train_index, test_index))
    return folds

def get_partition(vectors, labels, train_index, val_index):
    x_train = vectors[train_index]
    x_val = vectors[val_index]
    y_train = labels[train_index]
    y_val = labels[val_index]

    return x_train, y_train, x_val, y_val

def score_process(method, vec_files, labels, cv, out_format, process_num):
    vectors = files2vectors_seq(vec_files, out_format)
    dir_name, _ = os.path.splitext(vec_files[0])
    score_dir = dir_name + '/score/'

    if cv == 'j':
        folds_num = len(vectors)
    elif cv == '10':
        folds_num = 10
    else:
        folds_num = 5

    folds = partition_vectors(vectors, folds_num)
    count = 0
    for train_index, test_index in folds:
        x_train, y_train, x_test, y_test = get_partition(vectors, labels, train_index, test_index)
        train_mat = score4train_vec(method, x_train, process_num)
        test_mat = score4test_vec(method, x_train, x_test, process_num)
        count += 1
        temp_dir = score_dir + 'Fold%d/' % count
        if not os.path.exists(temp_dir):
            os.makedirs(temp_dir)

        FormatWrite(train_mat, out_format, temp_dir + 'train_score.txt').write_to_file()
        FormatWrite(test_mat, out_format, temp_dir + 'test_score.txt').write_to_file()

        np.savetxt(temp_dir + 'train_label.txt', y_train)
        np.savetxt(temp_dir + 'test_label.txt', y_test)
        np.savetxt(temp_dir + 'test_index.txt', test_index)

def ind_score_process(method, vectors, ind_vec_file, labels, ind_labels, out_format, process_num):
    ind_vectors = files2vectors_seq(ind_vec_file, out_format)
    dir_name, _ = os.path.split(ind_vec_file[0])
    score_dir = dir_name + '/ind_score/'
    train_mat = score4train_vec(method, vectors, process_num)
    test_mat = score4test_vec(method, vectors, ind_vectors, process_num)
    if not os.path.exists(score_dir):
        os.makedirs(score_dir)

    FormatWrite(train_mat, out_format, score_dir + 'train_score.txt').write_to_file()
    FormatWrite(test_mat, out_format, score_dir + 'test_score.txt').write_to_file()

    np.savetxt(score_dir + 'train_label.txt', labels)
    np.savetxt(score_dir + 'test_label.txt', ind_labels)


import numpy as np

def get_p(ls):
    '''
    Convert a value in log-space back to probability-space
    '''
    return np.exp(ls)

def get_ls(p):
    '''
    Convert a value probability-space to log-space
    '''
    if p == 0:
        return float("-inf")
    else:
        return np.log(p)

def ls_multiply(x, y):
    '''
    Multiply two values in log-space
    '''
    if (x == float("-inf")) or (y == float("-inf")):
        return float("-inf")
    else:
        return x + y

def ls_divide(x, y):
    '''
    Divide two values in log-space
    '''
    return x - y

def ls_add(x, y):
    '''
    Add two values in log-space
    '''
    if x == float("-inf"):
        return y
    elif y == float("-inf"):
        return x
    # TODO math.log1p may be a better choice or some numpy equivalent
    elif (x < y):
        return y + np.log(1 + np.exp(x - y))
    else:
        return x + np.log(1 + np.exp(y - x))


#!/usr/bin/env python3
"""Scipy examples."""
import numpy as np
from scipy import linalg
# pylint: disable=no-name-in-module
from scipy.special import comb, exp10, perm

# find combinations of 5, 2 values using comb(N, k)
COM = comb(5, 2, exact=False, repetition=True)
print(COM)

# find permutation of 5, 2 using perm (N, k) function
PER = perm(5, 2, exact=True)
print(PER)

EXP = exp10([1, 10])
print(EXP)

# define square matrix
TWO_D_ARRAY = np.array([[4, 5], [3, 2]])
# pass values to det() function
print(linalg.det(TWO_D_ARRAY))
print(linalg.inv(TWO_D_ARRAY))

ARR = np.array([[5, 4], [6, 3]])
# pass value into function
EG_VAL, EG_VECT = linalg.eig(ARR)
# get eigenvalues
print(EG_VAL)
# get eigenvectors
print(EG_VECT)
# pylint: enable=no-name-in-module


""" This script creates the file e_and_semi_axes.txt, which contains,
for each eccentricity e, the semi-major and semi-minor
axes for an ellipse with eccentricity e, and circumference 1
"""
from scipy import special #library for elliptical integrals
import numpy as np #library for math manipulations and functions
import math #another library for math manipulations and functions
import pandas as pd # for dataframe manipulations and creation, mostly to organize everything

semi_axis_dict = {}

for e in np.arange(0,1,0.01):
    a = 1/(4*special.ellipe(e**2)) # e**2 is our modulus
    b = a*math.sqrt((1-(e**2)))
    semi_axis_dict['%.2f'% e] = [a,b]

pd.DataFrame(semi_axis_dict).to_csv("e_and_semi_axes.txt", sep = '\t') #make the semi axis dict into a file


#! /usr/bin/env python
"""
File: sin_deriv.py

Copyright (c) 2016 Michael Seaman

License: MIT

Description: a module for plotting the approximated derivative of
a given function f alongside its analyitical derivative.
f(x, e) = sin((x+e)^-1)
f'(x, e) = -cos((x+e)^-1)/(x+e)^2
"""

import numpy as np
import matplotlib.pyplot as plt

def f(x, e):
    return np.sin(1/(x + e))

def f_prime(x, e):
    return -1 * np.cos(1/(x + e)) * (x + e)**-2

def diff(f, e, a, b, n):
    """
    Re-worked from classwork 5 -
    Takes a function f(x, e) and returns discrete values
    evaluated along its derivative.
    """
    f = np.vectorize(f)
    x = np.linspace(a, b, n+1)
    y = np.zeros(len(x))
    z = np.zeros(len(x))
    h = (b-a)/float(n)
    y = f(x, e)
    for i in xrange(len(x)-1):
        z[i] = (y[i+1] - y[i])/h
    z[n] = (y[n] - y[n-1])/h
    return z

def plot_alongside_derivative(f, f_prime, e, a, b, n):
    """
    Plots a function f(x, e) alongside its derivative
    f_prime(x, e) that is found through the finite
    difference formula from a to b with n points
    """
    f = np.vectorize(f)
    x = np.linspace(a, b, n+1)
    df = f_prime(x, e)
    df_approx = diff(f, e, a, b, n)
    plt.plot(x, df, 'g')
    plt.plot(x, df_approx, 'r')
    plt.title("Approx f'(x) [green] vs given f'(x) [red] for n = {} and e = {}".format(n, e))
    plt.show()


#
# Roots of Jacobi polynomials with parameters alpha and beta
#
#
# by Alberto Costa Nogueira Jr. (Matlab and Python versions)
#    Renato Cantao (Python version)
#
from math import cos, pi, fabs
from sys import float_info
from jacobi_p import jacobi_p
from djacobi_p import djacobi_p
import numpy

def jacobi_roots(m, alpha = 0.0, beta = 0.0):
    """Polynomial deflation to find the zeros of a Jacobi polynomial."""
    if m == 0:
        raise AssertionError("0-degree Jacobi polynomial detected!")
    elif m == 1:
        return numpy.array([(beta-alpha)/(alpha+beta+2.0)])
    else:
      x = numpy.zeros(m)      # Allocating vector for quadrature points

      # Newton-Raphson algorithm with polynomial deflation
      for k in range(0, m):
          # Initial guess: roots of the Chebyshev polynomial of order m
          r = -cos(pi*(2*k+1)/(2*m))
          if k > 0:
              r = 0.5*(r+x[k-1])
              #print("r = %s, x[%i] = %s" % (r, k, x[k]))
          delta = 1.0

          while fabs(delta) > float_info.epsilon:
              s = numpy.sum( 1.0/(r-x[range(0, k)]) )
              Pm  = jacobi_p(r, m, alpha, beta)
              DPm = djacobi_p(r, m, alpha, beta)
              delta = -Pm/(DPm-Pm*s)
              r = r+delta

          x[k] = r

    return x

#-- jacobi_roots.py ------------------------------------------------------------


import numpy as np

def cross_entropy(y_label, y_pred):
    """
    クロスエントロピー誤差を求める

    Parameters
    ----------
    y_label: array
        正解ラベル

    y_pred: array
        予測ラベル

    Returns
    -------
    loss: float
        クロスエントロピー誤差

    df: array
        出力層へ逆伝搬させる勾配
    """
    assert y_label.shape == y_pred.shape, "引数の形が不正です"
    loss = -np.sum(y_label * np.log(y_pred)) / y_label.shape[0]
    df = y_label
    return loss, df

def MSE(y, y_pred):
    """
    平均二乗誤差を求める

    Parameters
    ----------
    y: array
        正解の値

    y_pred: array
        予測された値

    Returns
    -------
    loss: float
        平均二乗誤差

    df: array
        出力層へ逆伝搬させる勾配
    """
    assert y.shape == y_pred.shape, "引数の形が不正です"
    loss = np.sum((y - y_pred)**2) / y.shape[0]
    df = -2*(y - y_pred)
    return loss, df


#
# Global Standardization
#
# The example below calculates the mean and
# standard deviations across all color channels in the loaded image,
# then uses these values to standardize the pixel values.

from PIL import Image
from numpy.core._asarray import asarray

image = Image.open('sydney_bridge.jpeg')
pixels = asarray(image)

print('Data Type: %s' % pixels.dtype)
print('Min: %.3f, Max: %.3f' % (pixels.min(), pixels.max()))

# Convert integers to floats
pixels = pixels.astype('float32')

# Calculate global mean and standard deviation
mean, std = pixels.mean(), pixels.std()
print('Mean: %.3f, Standrd Deviation: %.3f' % (mean, std))

#global standardization of pixels
pixels = (pixels - mean) / std

mean, std = pixels.mean(), pixels.std()
print('After Global Standardization ==>')
print('Mean: %.3f, Standrd Deviation: %.3f' % (mean, std))


#!/usr/bin/env python3
"""
0-likelihood.py
"""
import numpy as np

def likelihood(x, n, P):
    """
    function that calculates the likelihood of obtaining the data
    given various hypothetical probabilities of developing severe side effects
    """

    if not isinstance(P, np.ndarray) or P.ndim != 1:
        raise TypeError("P must be a 1D numpy.ndarray")
    if not isinstance(n, int) or n < 1:
        raise ValueError("n must be a positive integer")
    if not isinstance(x, int) or x < 0:
        err = "x must be an integer that is greater than or equal to 0"
        raise ValueError(err)
    if x > n:
        raise ValueError("x cannot be greater than n")
    if not all(P >= 0) or not all(P <= 1):
        raise ValueError("All values in P must be in the range [0, 1]")

    # Product of probabilities for the data:
    A = (P ** x) * ((1 - P) ** (n - x))
    # Factorials to be accounted for:
    B = np.math.factorial(x) * np.math.factorial(n - x) / np.math.factorial(n)
    L = A / B

    return L


import math
import numpy as np
import pytest
from pytest import approx

from regimedetection.src.metrics import euclidean_distance
from regimedetection.src.metrics import kl_divergence
from regimedetection.src.metrics import squared_euclidean_distance

def test_squared_euclidean_distance():
    a = [3, 4, 5]
    b = [10, 5, 0]

    assert squared_euclidean_distance(a, a) == approx(0)
    assert squared_euclidean_distance(a, b) == approx(75)

    assert squared_euclidean_distance(a, b) == squared_euclidean_distance(a, np.array(b))
    assert squared_euclidean_distance(a, b) == squared_euclidean_distance(a, tuple(b))

    with pytest.raises(ValueError):
        squared_euclidean_distance(a, str(a))

    with pytest.raises(ValueError):
        squared_euclidean_distance(a, [3, 4])

    # Scalar input
    assert squared_euclidean_distance(0, 2) == approx(4)
    assert squared_euclidean_distance(-1, -1) == approx(0)
    assert squared_euclidean_distance(5.25, 3.75) == approx(2.25)

def test_euclidean_distance():
    a = [1, 2, 3, 4]
    b = [6, 12, 18, 24]

    assert euclidean_distance(a, b) == approx(math.sqrt(squared_euclidean_distance(a, b)))

def slow_kl_divergence(a: np.ndarray, b: np.ndarray) -> np.float:
    """
    Non-vectorized, slow approach to calculating KL-divergence.
    Used only to compare vectorized approach

    a and b should be one-dimensional numpy arrays
    """

    if a.ndim != 1 or b.ndim != 1:
        raise ValueError("Expected one-dimensional np arrays in slow_kl_divergence")

    if a.shape != b.shape:
        raise ValueError("a and b must be vectors of the same length in slow_kl_divergence")

    ret = 0
    for a_entry, b_entry in zip(a, b):
        ret += a_entry * math.log(a_entry / b_entry)

    return ret

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

    slow_computation_result = np.empty(shape=(len(a)))
    for row_idx, (a_row, b_row) in enumerate(zip(a, b)):
        slow_computation_result[row_idx] = slow_kl_divergence(a_row, b_row)

    vectorised_computation_result = kl_divergence(a, b)

    assert np.allclose(slow_computation_result, vectorised_computation_result)

    # Check that appropriate shapes are handled correctly
    non_tiled_computation = kl_divergence(a, b[0])
    tiled_computation = kl_divergence(a, np.tile(b[0], reps=(len(a), 1)))
    assert np.allclose(non_tiled_computation, tiled_computation)

    non_tiled_computation_2 = kl_divergence(a[0], b)
    tiled_computation_2 = kl_divergence(np.tile(a[0], reps=(len(b), 1)), b)
    assert np.allclose(non_tiled_computation_2, tiled_computation_2)

    # Check that inappropriate shapes are handled correctly
    with pytest.raises(ValueError):
        kl_divergence(a, b[:2])

    with pytest.raises(ValueError):
        kl_divergence(a[:2], b)


from utils import calculate_correlation
import numpy as np

returns_general_motors = [0.018, -0.005, -0.047, -0.009, -0.012, 0.003, -0.027, -0.014, 0.029, -0.062, 0.009]
returns_ford = [0.002, -0.004, -0.027, -0.022, -0.001, 0.002, -0.006, -0.017, 0.035, -0.029, 0.002]
returns_exxon_mobil = [0.008, 0.015, 0.009, 0.012, 0.003, -0.007, 0.006, 0.005, -0.048, 0.025, -0.012]
returns_apple = [-0.002, 0.007, -0.004, -0.004, 0.002, 0.013, -0.011, 0.017, -0.001, 0.012, 0.006]

corr_gm_ford = calculate_correlation(returns_general_motors, returns_ford)
print('The correlation coefficient between General Motors and Ford is', corr_gm_ford)

corr_gm_exxon = calculate_correlation(returns_general_motors, returns_exxon_mobil)
print('The correlation coefficient between General Motors and ExxonMobil is ',  corr_gm_exxon)

corr_gm_appl = calculate_correlation(returns_general_motors, returns_apple)
print('The correlation coefficient between General Motors and Apple is ', corr_gm_appl)

corrcoef_matrix = np.corrcoef([returns_general_motors, returns_ford, returns_exxon_mobil, returns_apple])

print(corrcoef_matrix)


# import libs
import logging
import argparse
import pandas as pd
import numpy as np  # these are two libs for calculation

# set the logger
logging.basicConfig(
                    # filename = "logfile",
                    # filemode = "w+",
                    format='%(name)s %(levelname)s %(message)s',
                    datefmt = "%H:%M:%S",
                    level=logging.INFO)
logger = logging.getLogger("pagerank")

def pagerank(adj_mat, beta, epsilon, max_itr):
    """
    adj_mat: the adjacency matrix which represents the graph.
    beta: random teleport probability.
    epsilon: convergence epsilon.
    max_itr: the maximum iterations.
    return: rank_lst, a list containing the rank score for each node in the graph, list;
    """
    # number of nodes
    num_of_nodes = adj_mat.shape[0]
    # create the stochastic matrix=
    M_mat = np.transpose(adj_mat)
    # find the dead ends
    dead_ends = np.where(np.sum(M_mat, axis = 0) == 0)[0]
    # solution to dead ends
    if dead_ends:
        M_mat[:, dead_ends] = 1
    M_mat = M_mat / np.sum(M_mat, axis = 0)
    # solution to spider traps
    A_mat = beta * M_mat + (1 - beta) * (np.ones(M_mat.shape) / num_of_nodes)
    # init the rank_lst
    rank_lst = np.ones((num_of_nodes, 1)) / num_of_nodes
    # constantly updating rank_lst
    for i in range(max_itr):
        new_rank_lst = np.dot(A_mat, rank_lst)
        if np.linalg.norm(rank_lst - new_rank_lst) > epsilon:
            rank_lst = new_rank_lst
        else:
            break
    return rank_lst

def adjacency_mat(filepath):
    """
    filepath: the file path of edgelists file.
    return: adj_mat, the adjacency matrix created from edges, 2-d np.array;
    """
    # get edges from reading file
    edges_df = pd.read_table(filepath, header = None, sep = " ")
    # get the number of nodes
    num_of_nodes = np.max(edges_df.values)
    # init adjacency matrix
    adj_mat = np.zeros((num_of_nodes, num_of_nodes))
    for i, j in edges_df.values:
        adj_mat[i - 1, j - 1] = 1

    return adj_mat

def args_parser():
    """
    return: args containing programs arguments.
    """
    parser = argparse.ArgumentParser(description='PageRank')
    # set the random teleport probability
    parser.add_argument("--beta", type = float, default = 0.85,
                        help= "random teleport probability")

    # set the convergence epsilon
    parser.add_argument("--epsilon", type = float, default = 0.001,
                        help= "convergence epsilon")

    # set the maximum iterations
    parser.add_argument("--max_itr", type = int, default = 500,
                        help= "the maximum iterations when updating the ranks")

    # get edgelists
    parser.add_argument("--edges", type = str, default = "./edgelists.txt",
                        help= "path for edgelists file")

    # set if using debug mod
    parser.add_argument("-v", "--verbose", action= "store_true", dest= "verbose",
                        help= "enable debug info output")

    args = parser.parse_args()

    if args.beta > 1 or args.beta < 0:
        logger.warning("The value for random teleport probability is illegal. Turn to default 0.85")
        args.beta = 0.85

    if args.epsilon > 1:
        logger.warning("The value for convergence epsilon is too small. Turn to default 0.001")
        args.epsilon = 0.001

    if args.max_itr < 0:
        logger.warning("The value for maximum iterations is illegal. Turn to default 500")
        args.max_itr = 500

    return args

def main():
    # get program arguments
    args = args_parser()

    # set the logger
    logger.setLevel(logging.DEBUG)
    if not args.verbose:
        logger.setLevel(logging.INFO)
    logger.debug("--------DEBUG enviroment start---------")

    # check the input file format
    try:
        edges_df = pd.read_table(args.edges, header = None, sep = " ")
        if edges_df.dtypes[0] != np.dtype("int64") or edges_df.dtypes[1] != np.dtype("int64") or len(edges_df.columns) != 2 or np.sum(edges_df.values <= 0) != 0 or np.sum(edges_df.isnull().values):
            raise ValueError
    except:
        logger.warning("""The format of edgelists file is illegal. Plz change to 'int int' where two non-zero integer are separated by blankspace in each line
                          indicating there should an edge from first int to second int.
                       """)
        return

    adj_mat = adjacency_mat(args.edges)

    logger.info("------------Adjacency Matrix------------")
    logger.info("\n" + str(adj_mat))

    rank_lst = pagerank(adj_mat, args.beta, args.epsilon, args.max_itr)

    logger.info("------------PageRank Score-------------")
    for i, rank in enumerate(rank_lst):
        logger.info(str(i + 1) + "\t" + str(rank))
    logger.info("-------------Process Ends--------------")

if __name__ == "__main__":
    main()


from collections import Counter
from scipy.stats import entropy
import math

def info_gain(Ex, a, nan=True):
    """ Compute the information gain of an attribute a for given examples.

        Parameters
        ----------
        Ex : list of hashable
            A list of hashable objects (examples)
            corresponding to the given attributes a.
            I.e. a[i] <--> Ex[i].

        a : list of hashable
            A list of hashable objects (attributes)
            corresponding to the given examples Ex.
            I.e. a[i] <--> Ex[i].

        nan : boolean, default=True
            Boolean indicating how nan==nan should be evaluated.
            Default == True to avoid division by 0 errors.

        Returns
        -------
        result : float
            Information gain by knowing given attributes.

        """
    # Check whether examples and attributes have the same lengths.
    if len(Ex) != len(a):
        raise ValueError("Ex and a must be of the same size.")

    # Compute the entropy of examples
    H_Ex = entropy(list(Counter(Ex).values()))

    # If nan is True, replace all nan values in a by the string "__nan__"
    if nan:
        a = ['__nan__' if isinstance(x, float) and math.isnan(x) else x for x in a]

    # Compute the sum of all values v in a
    sum_v = 0
    for v in set(a):
        Ex_a_v = [x for x, t in zip(Ex, a) if t == v]
        sum_v += (len(Ex_a_v) / len(Ex)) *\
                 (entropy(list(Counter(Ex_a_v).values())))

    # Return result
    return H_Ex - sum_v

def intrinsic_value(Ex, a, nan=True):
    """ Compute the intrinsic value of an attribute a for given examples.

        Parameters
        ----------
        Ex : list of hashable
            A list of hashable objects (examples)
            corresponding to the given attributes a.
            I.e. a[i] <--> Ex[i].

        a : list of hashable
            A list of hashable objects (attributes)
            corresponding to the given examples Ex.
            I.e. a[i] <--> Ex[i].

        nan : boolean, default=True
            Boolean indicating how nan==nan should be evaluated.
            Default == True to avoid division by 0 errors.

        Returns
        -------
        result : float
            Intrinsic value of attribute a for samples Ex.

        """
    # Check whether examples and attributes have the same lengths.
    if len(Ex) != len(a):
        raise ValueError("Ex and a must be of the same size.")

    # If nan is True, replace all nan values in a by the string "__nan__"
    if nan:
        a = ['__nan__' if isinstance(x, float) and math.isnan(x) else x for x in a]

    # Compute the sum of all values v in a
    sum_v = 0
    for v in set(a):
        Ex_a_v = [x for x, t in zip(Ex, a) if t == v]
        sum_v += (len(Ex_a_v) / len(Ex)) * math.log(len(Ex_a_v) / len(Ex), 2)

    # Return result
    return -sum_v

def info_gain_ratio(Ex, a, nan=True):
    """ Compute the information gain ratio of an attribute a for given examples.

        Parameters
        ----------
        Ex : list of hashable
            A list of hashable objects (examples)
            corresponding to the given attributes a.
            I.e. a[i] <--> Ex[i].

        a : list of hashable
            A list of hashable objects (attributes)
            corresponding to the given examples Ex.
            I.e. a[i] <--> Ex[i].

        nan : boolean, default=True
            Boolean indicating how nan==nan should be evaluated.
            Default == True to avoid division by 0 errors.

        Returns
        -------
        result : float
            Information gain ratio by knowing given attributes.
            I.e. information gain normalised with intrinsic value calculation.

        """
    # Check whether examples and attributes have the same lengths.
    if len(Ex) != len(a):
        raise ValueError("Ex and a must be of the same size.")

    # Compute information gain ratio as IG/IV
    return info_gain(Ex, a, nan) / intrinsic_value(Ex, a, nan)


"""
Summary of the polynominal regression program:
1. Read dataset
2. observe top 5 data points
3. split the dataset into independent and dependent feature/variable
4. fit linear regression model with polynomial degree of 1 (default i,e linear regression)
5. fit linear regression model with polynomial degree of 2 and 4
6. plot the linear regressions fit to observe how model has fitted on data
"""

# import lib's
import pandas as pd
import numpy  as np
import matplotlib.pyplot as plt

# import the dataset
dataset= pd.read_csv('case_study2_position_salary/Position_Salaries.csv') # the full namespace of the file can be provided if the file is not in the same directory as the .ipynb or.py file

dataset.head()   # to see how the imported data looks like
"""
            Position  Level  Salary
0   Business Analyst      1   45000
1  Junior Consultant      2   50000
2  Senior Consultant      3   60000
3            Manager      4   80000
4    Country Manager      5  110000

- Here, it can be seen that there are 3 columns in the dataset.
- The problem statement here is to predict the salary based on the Position and Level of the employee.
- But we may observe that the Position and the level are related or level is one other way of conveying the position
        of the employee in the company. So, essentially Position and Level are conveying the same kind of information.
        As Level is a numeric column,
        let's use that in our Machine Learning Model. Hence, Level is our feature or X variable.
        And, Salary is Label or the Y variable """

x=dataset.iloc[:,1:2].values

# x=dataset.iloc[:,1].values
# this is written in this way to make x as a matrix as the machine learning algorithm.
# if we write 'x=dataset.iloc[:,1].values', it will return x as a single-dimensional array which is not desired
x
""" array([[ 1],
       [ 2],
       [ 3],
       [ 4],
       [ 5],
       [ 6],
       [ 7],
       [ 8],
       [ 9],
       [10]], dtype=int64) """

y=dataset.iloc[:,2].values
y
""" array([  45000,   50000,   60000,   80000,  110000,  150000,  200000,
        300000,  500000, 1000000], dtype=int64) """

"""
Generally, we divide our dataset into two parts
1) The training dataset to train our model. And,
2) The test dataset to test our prepared model.

- Here, as the dataset has a limited number of entries, we won't do a split.
- Instead of that, we'd use direct numerical values to test the model.
- Hence, the code above is kept commented. But, train test split can also be done, if you desire so:)

- To learn Polynomial Regression, we'd follow a comparative approach.
- First, we'll try to create a Linear Model using Linear Regression and then we'd prepare a Polynomial
                Regression Model and see how do they compare to each other
 """

# Fitting Linear Regression to the dataset
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(x, y)
# LinearRegression()

# Visualising the Linear Regression results
plt.scatter(x, y, color = 'red')
plt.plot(x, lin_reg.predict(x), color = 'blue')
plt.title('Linear Regression')
plt.xlabel('Position level')
plt.ylabel('Salary')
plt.show()

"""
- Here, the red dots are the actual data points and, the blue straight line is what our model has created.
- It is evident from the diagram above that a Linear model does not fit our dataset well.
- So, let's try with a Polynomial Model.
 """

# Fitting Polynomial Regression to the dataset
from sklearn.preprocessing import PolynomialFeatures
poly_reg = PolynomialFeatures(degree = 2)  # trying to create a 2 degree polynomial equation. It simply squares the x as shown in the output
X_poly = poly_reg.fit_transform(x)

print(X_poly)
"""
[[  1.   1.   1.]
 [  1.   2.   4.]
 [  1.   3.   9.]
 [  1.   4.  16.]
 [  1.   5.  25.]
 [  1.   6.  36.]
 [  1.   7.  49.]
 [  1.   8.  64.]
 [  1.   9.  81.]
 [  1.  10. 100.]] """

poly_reg.fit(X_poly, y)
# PolynomialFeatures()

# doing the actual polynomial Regression
lin_reg_2 = LinearRegression()
lin_reg_2.fit(X_poly, y)
LinearRegression()

"""
- It can be noted here that for Polynomial Regression also, we are using the Linear Regression Object.

Why is it so?
- It is because the Linear in Linear Regression does not talk about the degree of the Polynomial equation in terms
                of the dependent variable(x).
                Instead, it talks about the degree of the coefficients. Mathematically,
                y=a+bx+cx2+...+nxn+...

- It's not talking about the power of x, but the powers of a,b,c etc.
- And as the coefficients are only of degree 1, hence the name Linear Regression.
 """

# Visualising the Polynomial Regression results
plt.scatter(x, y, color = 'red')
plt.plot(x, lin_reg_2.predict(poly_reg.fit_transform(x)), color = 'blue')
plt.title('Polynomial Regression')
plt.xlabel('Position level')
plt.ylabel('Salary')
plt.show()

"""
- Still, a two degree equation is also not a good fit.
- Now, we'll try to increase the degree of the equation i.e. we'll try to see that whether we get a good fit at a
                higher degree or not.
                After some hit and trial, we see that the model get's the best fit for a 4th degree polynomial equation.
 """

# Fitting Polynomial Regression to the dataset
poly_reg1 = PolynomialFeatures(degree = 4)
X_poly1 = poly_reg1.fit_transform(x)
poly_reg1.fit(X_poly, y)
lin_reg_3 = LinearRegression()
lin_reg_3.fit(X_poly1, y)
# LinearRegression()

# Visualising the Polynomial Regression results
plt.scatter(x, y, color = 'red')
plt.plot(x, lin_reg_3.predict(poly_reg1.fit_transform(x)), color = 'blue')
plt.title('Polynomial Regression of Degree 4')
plt.xlabel('Position level')
plt.ylabel('Salary')
plt.show()

"""
- Here, we can see that our model now accurately fits the dataset.
- This kind of a fit might not be the case with the actual business datasets.
- we are getting a brilliant fit as the number of datapoints are a few. """


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Program: diffusionEq2D_ForwardEulerV
Created: Aug 2020
@author: Ryan Clement (RRCC)
         scisoft@outlook.com

Purpose: Solve the partial differential equation (PDE)

            u_t = alpha * (u_xx + u_yy)

in (0,Lx)x(0,Ly) with vanishing boundary conditions or

            u = 0 for x = 0, y in [0,Ly]
            u = 0 for x = 1.0, y in [0,Ly]
            u = 0 for y = 0, x in [0,Lx]
            u = 0 for y = 1.0, x in [0,Lx]

and initial condition

            u(x,y,0) = I(x,y) = A*sin(Pi*x/Lx)*sin(Pi*y/Ly)

The analytic solution for this problem is given by

            u(x,y,t) = Ae**(-alpha*Pi**2*(Lx**-2 + Ly**-2)*t)*sin(Pi*x/Lx)*sin(Pi*y/Ly)

We will take A=Lx=Ly=1 for this simulation
"""

### IMPORTS
import numpy as np
import matplotlib.pyplot as plt

### FUNCTIONS
def initialCondition(xN,yN,X,Y):
    return np.sin(np.pi*X/xN) * np.sin(np.pi*Y/yN)

def analSol(a,xN,yN,X,Y,t):
    return np.exp(-a * np.pi**2 * (1.0/xN**2 + 1.0/yN**2) * t) * np.sin(np.pi*X/xN) * np.sin(np.pi*Y/yN)

def plotInitialCondition(a,xN,yN,X,Y):
    # fig  = plt.figure()
    ax   = plt.axes(projection="3d")
    Z    = initialCondition(xN,yN,X,Y)
    # ax.plot_wireframe(X,Y,Z,color='green')
    ax.plot_surface(X,Y,Z,rstride=1,cstride=1,cmap='hsv',edgecolor='none')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('u(x,y)')
    ax.set_title('Initial Condition')
    ax.set_zlim(0,1)
    plt.show()

def plotSol(X,Y,u,tit):
    ax   = plt.axes(projection="3d")
    # ax.plot_wireframe(X,Y,u,color='green')
    ax.plot_surface(X,Y,u,rstride=1,cstride=1,cmap='hsv',edgecolor='none')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('u(x,y)')
    ax.set_title(tit)
    ax.set_zlim(0,1)
    plt.show()

def plotDiff(X,Y,u,tit):
    ax   = plt.axes(projection="3d")
    ax.plot_wireframe(X,Y,u,color='green')
    # ax.plot_surface(X,Y,u,rstride=1,cstride=1,cmap='winter',edgecolor='none')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel(r'$\frac{\Delta u}{u}$')
    ax.set_title(tit)
    plt.show()

def setIC_Loop(a,x,y,u,xPts,yPts,xN,yN):
    for j in range(yPts):
        for i in range(xPts):
            u[i,j] = initialCondition(a, xN, yN, x[i], y[j])

def setIC(xN,yN,X,Y):
    u = initialCondition(xN, yN, X, Y)
    # Clean up boundary
    u[0,:]  = 0.0
    u[-1,:] = 0.0
    u[:,0]  = 0.0
    u[:,-1] = 0.0
    return u

def advanceN(a,xN,yN,u,n,xPts,yPts):
    ### VARIABLES
    x0   = 0.0                        # X: Minimum value (Left boundary)
    dx   = (xN - x0)/(xPts-1)         # X: Distance between mesh points
    dxs  = dx**2
    y0   = 0.0                        # Y: Minimum value (Bottom boundary)
    dy   = (yN - y0)/(yPts-1)         # Y: Distance between mesh points
    dys  = dy**2
    tsc  = 10                         # Time step control parameter
    dtx  = dxs/(tsc*a)                # X: Temporal mesh interval
    dty  = dys/(tsc*a)                # Y: Temporal mesh interval
    dt   = min(dtx, dty)
    at   = a*dt
    dpx  = at/dxs                     # Dimensionless parameter
    dpy  = at/dys                     # Dimensionless parameter
    uO   = np.copy(u)                 # Previous time-step value array (shallow copy)
    for k in range(n):
        for j in range(1,yPts-1):
            for i in range(1,xPts-1):
                u[i,j] = uO[i,j] + \
                         dpx*(uO[i+1,j] - 2.0*uO[i,j] + uO[i-1,j]) + \
                         dpy*(uO[i,j+1] - 2.0*uO[i,j] + uO[i,j-1])
        # Boundary Condition
        u[0,:]  = 0.0
        u[-1,:] = 0.0
        u[:,0]  = 0.0
        u[:,-1] = 0.0
        uO, u = u, uO
    return uO,dt

### MAIN
a    = 1.0
xN   = 1.0
yN   = 1.0
xPts = 51
yPts = 51
x    = np.linspace(0,xN,xPts)
y    = np.linspace(0,yN,yPts)
X, Y = np.meshgrid(x,y)
u = setIC(xN,yN,X,Y)
plotSol(X,Y,u,'Initial Condition')
numSteps = 1000
uS,dt = advanceN(a,xN,yN,u,numSteps,xPts,yPts)
print('Time Step = {:0.5f} s'.format(dt))
time = numSteps*dt
print('Simulation End Time = {:0.5f} s'.format(time))
plotSol(X,Y,uS,'Simulation Time {:.5f} s'.format(time))
uA = analSol(a,xN,yN,X,Y,time)
plotSol(X,Y,uA,'Analytic Time {:.5f} s'.format(time))
uFracDiff = np.copy(uA)
for j in range(yPts):
    for i in range(xPts):
        ua = uA[i,j]
        us = uS[i,j]
        if 0.0 == ua or 0.0 == us:
            uFracDiff[i,j] = 0.0
        else:
            uFracDiff[i,j] = ua/us - 1.0
plotDiff(X,Y,uFracDiff,'Fractional Difference')


import numpy as np
from scipy.optimize import bisect

TOLERANCE = 1e-6

def proj_l1(x, r = 1, method = "bisection"):
    if np.isscalar(x):
        x = np.array([x])
    if not np.isscalar(r) or r < 0:
        raise ValueError("r must be a non-negative scalar.")
    if np.linalg.norm(x,1) <= r:
        return x
    else:
        beta = proj_simplex(np.abs(x), r, method)
        return np.sign(x) * beta

def proj_simplex(x, r = 1, method = "bisection"):
    """Project x onto a simplex with upper bound r.
       Bisection: Liu and Ye (2009). "Efficient Euclidean Projections in Linear Time." Sect. 2.1.
            https://icml.cc/Conferences/2009/papers/123.pdf
       Efficient: Duchi et al (2008). "Efficient Projections onto the l1-Ball for Learning in High Dimensions."
            Fig. 1 and Sect. 4.
            https://stanford.edu/~jduchi/projects/DuchiShSiCh08.pdf
    """
    if np.isscalar(x):
        x = np.array([x])
    if not np.isscalar(r) or r < 0:
        raise ValueError("r must be a non-negative scalar.")
    elif r == 0:
        return np.zeros(x.shape)

    if method == "bisection":
        c_min = np.min(x) - (r + TOLERANCE)/x.size
        c_max = np.max(x) + (r + TOLERANCE)/x.size
        c_star = bisect(lambda c: np.sum(np.maximum(x - c, 0)) - r, c_min, c_max)
    elif method == "sorted":
        x_decr = np.sort(x, axis = None)[::-1]
        x_cumsum = np.cumsum(x_decr)
        denom = np.arange(1, x_decr.size + 1)
        theta = (x_cumsum - r)/denom
        x_diff = x_decr - theta
        idx = np.max(np.argwhere(x_diff > 0).ravel())
        c_star = theta[idx]
    else:
        raise ValueError("method must be either 'bisection' or 'sorted'.")
    return np.maximum(x - c_star, 0)


import sys
import numpy as np

def minCostPath(cost,m,n):

	# cost is the matrix we are trying to traverse

	# m is the row idx of cost matrix
	# n is the column index of the cost matrix

	if (n < 0) or (m < 0):
		return sys.maxsize
	elif n == 0 and m == 0:
		return cost[m][n]
	else:
		return cost[m][n] + min (minCostPath(cost,m-1,n),\
			minCostPath(cost,m,n-1),minCostPath(cost,m-1,n-1))

cost = [[1, 2, 3],
		[4, 7, 20],
		[1, 4, 3]]

print(minCostPath(cost,2,2))


import numpy as np

def rand_centroids(X, K):
    rand_indices = np.arange(len(X))
    np.random.shuffle(rand_indices)
    centroids = X[rand_indices][:K]
    return centroids

def find_closest_centroids(X, centroids):
    distances = np.array([np.sqrt((X.T[0] - c[0])**2 + (X.T[1] - c[1])**2) for c in centroids])
    return distances.argmin(axis=0)

def compute_means(X, centroid_idx, K):
    centroids = []
    for k in range(K):
        t = X[centroid_idx == k]
        c = np.mean(t, axis=0) if t.size > 0 else np.zeros((X.shape[1],))
        centroids.append(c)

    return np.array(centroids)

def run_k_means(X, K, num_iter=10):
    centroids = rand_centroids(X, K)
    centroids_history = [centroids]

    for i in range(num_iter):
        centroid_idx = find_closest_centroids(X, centroids)
        centroids = compute_means(X, centroid_idx, K)
        centroids_history.append(centroids)

    return centroids, centroid_idx, centroids_history

def k_means_distortion(X, centroids, idx):
    K = centroids.shape[0]
    distortion = 0

    for i in range(K):
        distortion += np.sum((X[idx == i] - centroids[i])**2)

    distortion /= X.shape[0]
    return distortion

def find_best_k_means(X, K, num_iter=100):
    result = np.inf
    r_centroids = None
    r_idx = None
    r_history = None

    for i in range(num_iter):
        centroids, idx, history = run_k_means(X, K)
        d = k_means_distortion(X, centroids, idx)

        if d < result:
            print(f'> [{i}]: k-means improved with distortion: {d}')
            r_centroids = centroids
            r_idx = idx
            r_history = history
            result = d

    return r_centroids, r_idx, r_history


# Python code for Mandelbrot Fractal

# Import necessary libraries
from PIL import Image
import numpy as np
import colorsys
import time
import os
import sys

# setting the width and height of the output image
# 460 480
# 720 576
# 720 480
# 1280 720
# 1440 1080
# 1920 1080
# 2048 1080
# 3840 2160
# 4096 2160

WIDTH = 1280
HEIGHT = 720

# a function to return a tuple of colors
# as integer value of rgb

# def rgb(i):
#     red = int(i / 255.0)
#     green = int(1.0+i/10.0)
#     blue = int(0.5*i/100.0)
#     color = (red, green, blue)
#     return color

def rgb(i):
    color = 255 * np.array(colorsys.hsv_to_rgb(i / 255.0, 0.85, i/12.0))
    return tuple(color.astype(int))

# function defining a mandelbrot
def mandelbrot(x, y, d):
    c0 = complex(x, y)
    c = 0
    for i in range(1, 100):
        if abs(c) > 2:
            return rgb(i)
        c = c**d + c0
    return (0, 0, 0)

# Main function

def main(img_number, path):
    # creating the new image in RGB mode
    img = Image.new('RGB', (WIDTH, HEIGHT))
    pixels = img.load()

    start = time.time()
    for x in range(img.size[0]):

        # displaying the progress as percentage
        print("{:.2f} %".format(x / WIDTH * 100.0))

        for y in range(img.size[1]):
            pixels[x, y] = mandelbrot((x - (0.65 * WIDTH)) / (0.2*WIDTH),
                                      (y - (0.5*HEIGHT))/(0.3*HEIGHT), (1/24)*img_number)

    # to save and display the created fractal after
    # completing the given number of iterations

    if(sys.platform in ['linux', 'linux2']):
        # Save image for linux system
        if(img_number < 10):
            img.save(path + "/Images/Mandelbrot/"+"im_00{}.jpg".format(img_number))
        elif (img_number < 100):
            img.save(path + "/Images/Mandelbrot/"+"im_0{}.jpg".format(img_number))
        else:
            img.save(path + "/Images/Mandelbrot/"+"im_{}.jpg".format(img_number))
    if(sys.platform in ['win32', 'cygwin', 'msys']):
        # Save image for window system
        if(img_number < 10):
            img.save(path+"\Images\Mandelbrot\im_00{}.jpg".format(img_number))
        elif(img_number < 100):
            img.save(path+"\Images\Mandelbrot\im_0{}.jpg".format(img_number))
        else:
            img.save(path+"\Images\Mandelbrot\im_{}.jpg".format(img_number))

    end = time.time()
    print("time is ", end-start)

if __name__ == '__main__':

    # get the path to the current python file directory
    path = os.path.dirname(os.path.realpath(__file__))

    # Frame count
    start_frame = 1
    end_frame = 24*5

    for img_number in range(start_frame,end_frame):
        main(img_number, path)


"""
Programming Project 10
Ankit Khandelwal
15863
"""

from cmath import exp
from math import sqrt, asin, sin, cos
from random import random

import numpy as np

N = 1

psi = np.ndarray((2 ** N, 1), complex)  # N-qubit register
comp_state = 0
for i in range(2 ** N):
    if i == comp_state:
        psi[i] = 1
    else:
        psi[i] = 0
psi_state_0 = np.copy(psi)

error_prob_percent = 10
error = asin(sqrt(error_prob_percent / 100))

H = 1 / sqrt(2) * np.array([[1, 1], [1, -1]])  # Hadamard gate

# X Error
EX = np.array(([cos(error), -sin(error) * 1j], [-sin(error) * 1j, cos(error)]), complex)
# Z Error
EZ = np.array(([exp(-error * 1j), 0], [0, exp(error * 1j)]), complex)

X = np.array(([0, 1], [1, 0]), int)  # X gate
Z = np.array(([1, 0], [0, -1]), int)  # Z gate

repeat = 1000
result = np.ndarray((6, repeat), np.ndarray)

print('Error prob is {} and correct answer is 0.\n'.format(error_prob_percent))
for k in range(repeat):
    PSI = []
    psi = np.copy(psi_state_0)

    # for project 10 circuit (a)
    PSI.append(H @ H @ psi)
    psi = np.copy(psi_state_0)

    # for project 10 circuit (b)
    PSI.append(H @ H @ EX @ psi)
    psi = np.copy(psi_state_0)

    # for project 10 circuit (c)
    PSI.append(H @ H @ X @ psi)
    psi = np.copy(psi_state_0)

    # for project 10 circuit (d)
    PSI.append(H @ EZ @ H @ psi)
    psi = np.copy(psi_state_0)

    # for project 10 circuit (e)
    PSI.append(H @ Z @ H @ psi)
    psi = np.copy(psi_state_0)

    # for project 10 circuit (f)
    PSI.append(H @ EZ @ H @ EX @ psi)

    for n in range(6):
        r = random()
        q = 0
        for i in range(2 ** N):
            q = q + abs(PSI[n][i]) ** 2
            if r < q:
                result[n][k] = i
                break
fig = ['a', 'b', 'c', 'd', 'e', 'f']
for n in range(6):
    result_freq = np.zeros((2 ** N), int)
    print('For project 10 circuit({})'.format(fig[n]))
    for i in range(2 ** N):
        result_freq[i] = np.count_nonzero(result[n][:] == i)
        print(format(i, '0{}b'.format(N)), 'occured', result_freq[i], 'times.', (result_freq[i] / repeat * 100),
              'percent.')
    print('***')


"""
Problem Statement

inner

The inner tool returns the inner product of two arrays.

import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print numpy.inner(A, B)     #Output : 4
outer

The outer tool returns the outer product of two arrays.

import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print numpy.outer(A, B)     #Output : [[0 0]
                            #          [3 4]]
Task

You are given two arrays: A and B.
Your task is to compute their inner and outer product.

Input Format

The first line contains the space separated elements of array A.
The second line contains the space separated elements of array B.

Output Format

First, print the inner product.
Second, print the outer product.

Sample Input

0 1
2 3
Sample Output

3
[[0 0]
 [2 3]]
"""
import numpy

a = numpy.array(map(int, raw_input().split()), dtype=numpy.int)
b = numpy.array(map(int, raw_input().split()), dtype=numpy.int)

print numpy.inner(a, b)
print numpy.outer(a, b)


"""
Defines functions used to construct basis vectors, convert between coordinate systems, build meshes, etc.
"""

import miepy
import numpy as np

def sph_to_cart(r, theta, phi, origin=None):
    """convert spherical coordinates (r, theta, phi) centered at origin to cartesian coordinates (x, y, z)"""
    if origin is None:
        origin = np.zeros(3, dtype=float)

    x = origin[0] + r*np.sin(theta)*np.cos(phi)
    y = origin[1] + r*np.sin(theta)*np.sin(phi)
    z = origin[2] + r*np.cos(theta)

    return x,y,z

def cart_to_sph(x, y, z, origin=None):
    """convert cartesian coordinates (x, y, z) to spherical coordinates (r, theta, phi) centered at origin"""
    if origin is None:
        origin = np.zeros(3, dtype=float)

    x0,y0,z0 = origin
    r = ((x - x0)**2 + (y - y0)**2 + (z - z0)**2)**0.5
    theta = np.arccos((z - z0)/r)
    phi = np.arctan2(y - y0, x - x0)

    return r, theta, phi

def cyl_to_cart(r, phi, z, origin=None):
    """convert cylindrical coordinates (r, phi, z) centered at origin to cartesian coordinates (x, y, z)"""
    if origin is None:
        origin = np.zeros(3, dtype=float)

    x = origin[0] + r*np.cos(phi)
    y = origin[1] + r*np.sin(phi)

    return x, y, z + origin[2]

def cart_to_cyl(x, y, z, origin=None):
    """convert cartesian coordinates (x, y, z) to cylindrical coordinates (r, phi, z) centered at origin"""
    if origin is None:
        origin = np.zeros(3, dtype=float)

    x0,y0,z0 = origin
    r = ((x - x0)**2 + (y - y0)**2)**0.5
    phi = np.arctan2(y - y0, x - x0)

    return r, phi, z - z0

#TODO: if theta is scalar, or phi is scalar... same with other functions here
#TODO: implement origin
def sph_basis_vectors(theta, phi, origin=None):
    """obtain the spherical basis vectors (r_hat, theta_hat, phi_hat) for given theta, phi"""
    if origin is None:
        origin = np.zeros(3, dtype=float)

    r_hat = np.array([np.sin(theta)*np.cos(phi), np.sin(theta)*np.sin(phi), np.cos(theta)])
    theta_hat = np.array([np.cos(theta)*np.cos(phi), np.cos(theta)*np.sin(phi), -1*np.sin(theta)])
    phi_hat = np.array([-1*np.sin(phi), np.cos(phi), np.zeros_like(phi)])

    return r_hat, theta_hat, phi_hat

#TODO: implement origin
def vec_cart_to_sph(F, theta, phi, origin=None):
    """convert a vector field F from cartesian to spherical coordinates

    Arguments:
        F[3,...]     vector field values
        theta        theta coordinates
        phi          phi coordinates
    """
    if origin is None:
        origin = np.zeros(3, dtype=float)

    Fsph = np.zeros_like(F)
    r_hat, theta_hat, phi_hat = sph_basis_vectors(theta, phi)
    Fsph[0] = np.sum(F*r_hat, axis=0)
    Fsph[1] = np.sum(F*theta_hat, axis=0)
    Fsph[2] = np.sum(F*phi_hat, axis=0)

    return Fsph

#TODO: implement origin
def vec_sph_to_cart(F, theta, phi, origin=None):
    """convert a vector field F from spherical to cartesian coordinates

    Arguments:
        F[3,...]     vector field values
        theta        theta coordinates
        phi          phi coordinates
    """
    if origin is None:
        origin = np.zeros(3, dtype=float)

    Fcart = np.zeros_like(F)
    r_hat, theta_hat, phi_hat = sph_basis_vectors(theta, phi)
    for i in range(3):
        Fcart[i] = F[0]*r_hat[i] + F[1]*theta_hat[i] + F[2]*phi_hat[i]

    return Fcart

def sphere_mesh(sampling):
    """
    Obtain a THETA,PHI mesh for discretizing the surface of the sphere, consistent
    with the format required by the project and decompose functions
    Returns (THETA,PHI) meshgrids

    Arguments:
        sampling   number of points to sample between 0 and pi
    """

    phi = np.linspace(0, 2*np.pi, 2*sampling)
    tau = np.linspace(-1, 1, sampling)
    theta = np.arccos(tau)

    THETA,PHI = np.meshgrid(theta, phi, indexing='ij')
    return THETA, PHI

def cart_sphere_mesh(radius, origin, sampling):
    """Given a radius, origin and sampling, return the
    X,Y,Z,THETA,PHI,tau,phi coordinates of a discretized sphere"""

    r = np.array([radius])
    tau = np.linspace(-1,1, sampling)
    theta = np.arccos(tau)
    phi = np.linspace(0, 2*np.pi, 2*sampling)
    R, THETA, PHI = np.meshgrid(r,theta,phi, indexing='ij')

    X = origin[0] + R*np.sin(THETA)*np.cos(PHI)
    Y = origin[1] + R*np.sin(THETA)*np.sin(PHI)
    Z = origin[2] + R*np.cos(THETA)

    return map(np.squeeze, (X,Y,Z,THETA,PHI,tau,phi))

def rotate(x, y, z, quat, origin=None):
    """Rotate the points (x, y, z) around an origin using a quaternion"""
    if origin is None:
        origin = np.zeros(3, dtype=float)

    R = miepy.quaternion.as_rotation_matrix(quat)
    p = np.asarray(translate(x, y, z, -origin))
    p_rotated = np.einsum('ij,j...->i...', R, p)
    p_final = translate(*p_rotated, origin)

    return p_final

def rotate_sph(theta, phi, quat):
    """Rotate the spherical coordinates (theta, phi) to rotated spherical coordinates"""
    q1 = miepy.quaternion.from_spherical_coords(theta, phi)
    q2 = quat*q1
    theta_r, phi_r = np.moveaxis(miepy.quaternion.as_spherical_coords(q2), -1, 0)

    theta_r = np.asarray(theta_r)
    phi_r = np.asarray(phi_r)

    # Final step: if theta = 0, then above conversion turns phi -> phi_r/2, so this is corrected
    idx = (theta == 0)
    phi_r[idx] *= 2

    return theta_r, phi_r

def rotate_vec(F, quat):
    """Rotate the vector F using a quaternion"""
    R = miepy.quaternion.as_rotation_matrix(quat)
    F_rotated = np.einsum('ij,j...->i...', R, F)

    return F_rotated

def translate(x, y, z, dr):
    """Translate the points (x, y, z) by dr"""
    xp = x + dr[0]
    yp = y + dr[1]
    zp = z + dr[2]

    return np.asarray([xp, yp, zp])


# binomial_distribution is a discrete distribution

# it describes the outcome of binary scenarios,
# e.g. toss of a coin, it will either be head or till
# it has three parameters
# n - number of trials
# p - probability of occurence of each trial(e.g for toss of a coin = 0.5 for each)
# size - the shape of the returned array

# Discrebe Distribution -
# the distribution is defined at separate set of events.

from numpy import random

x = random.binomial(n = 10, p = 0.5, size = 10)

print(x)

# visualization of binomial distribution

import matplotlib.pyplot as plt
import seaborn as sns

sns.distplot(random.binomial(n = 10, p = 0.5, size = 1000), hist = True, kde = False)
plt.show()

# difference between normal and binomial distribution

"""
The main difference is that normal distribution is continuous
whereas binomial is discrete, but if there are enough data points
it will be quite similar to normal distribution will certain loc, and scale
"""
# from numpy import random
# import matplotlib.pyplot as plt
# import seaborn as sns

sns.distplot(random.normal(loc = 50, scale = 5, size = 1000), hist = False, label = 'normal')
sns.distplot(random.binomial(n = 100, p = 0.5, size = 1000), hist = False, label = 'binomial')
plt.show()


import numpy as np
from utils import tour_cost
from itertools import combinations
import time

def two_opt(cost, tour, iterations):
    opt_tour = tour.copy()
    for _ in range(iterations):
        for i in range(len(opt_tour)):
            for j in range(i + 2, len(opt_tour)-1):
                if cost[opt_tour[i], opt_tour[i+1]] + cost[opt_tour[j], opt_tour[j+1]] > cost[opt_tour[i], opt_tour[j]] + cost[opt_tour[i+1], opt_tour[j+1]]:
                    opt_tour[i+1:j+1] = reversed(opt_tour[i+1:j+1])

        if tour_cost(cost, opt_tour) < tour_cost(cost, tour):
                    tour = opt_tour
    return tour

if __name__ == "__main__":

    # node_array = np.random.random_integers(0,high=10,size=(10,2))

    start_time = time.clock()
    # adj_matrix = distance_matrix(node_array, node_array, p=2)

    # cost = np.random.random_integers(0,high=100,size=(100,100))
    cost = np.array([[ 0, 32, 53, 51, 84, 72, 76, 33, 33, 64],
                     [32,  0, 21, 29, 76, 40, 43, 41, 36, 37],
                     [53, 21,  0, 23, 72, 19, 23, 52, 46, 22],
                     [51, 29, 23,  0, 50, 35, 39, 36, 30, 14],
                     [84, 76, 72, 50,  0, 79, 83, 51, 51, 53],
                     [72, 40, 19, 35, 79,  0,  4, 69, 63, 26],
                     [76, 43, 23, 39, 83,  4,  0, 74, 67, 30],
                     [33, 41, 52, 36, 51, 69, 74,  0,  6, 50],
                     [33, 36, 46, 30, 51, 63, 67,  6,  0, 44],
                     [64, 37, 22, 14, 53, 26, 30,  50, 44, 0]])

    tour = [i for i in range(len(cost))]
    tour.extend([tour[0]])
    print('Original Tour:', tour)
    print('Original Tour Cost:', tour_cost(cost, tour))
    iterations = 1
    opt2_tour = two_opt(cost, tour, iterations)
    print('2-opt Tour:', opt2_tour)
    print('2-opt Tour Cost:', tour_cost(cost, opt2_tour))

    print('Computation Time:',(time.clock() - start_time))


# CubicSpline.py
#
# Daniel R. Reynolds
# SMU Mathematics
# Math4315

# imports
import numpy
from LUFactorsTri    import LUFactorsTri
from BackwardSubTri  import BackwardSubTri
from ForwardSubTri   import ForwardSubTri

def CubicSplineCoeffs(t, y):
    """
    Usage: z = CubicSplineCoeffs(t, y)

    This routine computes the coefficients of the natural interpolating cubic
    spline through the data values (t_k,y_k), k=0,...,n.

    Inputs:   t - array of interpolation knots
              y - array of interpolation values
    Outputs:  z - cubic spline coefficients
    """

    # check that dimensions of t and y match
    if (numpy.shape(t) != numpy.shape(y)):
        raise ValueError("CubicSplineCoeffs error: node and data value array inputs must have identical size")

    # get overall number of knots
    n = t.size-1

    # set knot spacing array
    h = t[1:n+1] - t[0:n]

    # set diagonal values
    d = 2.0 * ( h[0:n-1] + h[1:n] )

    # set right-hand side values
    b = 6.0/h*( y[1:n+1] - y[0:n] )
    v = b[1:n] - b[0:n-1]

    # set up tridiagonal linear system, A*z=V
    A = numpy.zeros((n+1,n+1))
    V = numpy.zeros(n+1)
    for i in range(1,n):
        A[i,i-1] = h[i-1]
        A[i,i]   = d[i-1]
        A[i,i+1] = h[i]
        V[i]     = v[i-1]

    # set up first and last rows of linear system to enforce natural boundary conditions
    A[0,0] = 1.0
    V[0] = 0.0
    A[n,n] = 1.0
    V[n] = 0.0

    # solve linear system for result (using tridiagonal solvers from homework 2)
    L, U = LUFactorsTri(A)
    z = BackwardSubTri(U, ForwardSubTri(L, V))

    return z

def CubicSplineEvaluate(t, y, z, x):
    """
    Usage: s = CubicSplineEvaluate(t, y, z, x)

    This routine evaluates the cubic spline defined by the knots, t, the data
    values, y, and the coefficients, z, at the point x.

    Inputs:   t - array of interpolation knots
              y - array of interpolation values
              z - cubic spline coefficients
              x - evaluation point(s)
    outputs:  s - value of cubic spline at point(s) x
    """

    # check input arguments
    if ((t.size != y.size) or (t.size != z.size)):
        raise ValueError("CubicSplineCoeffs error: node and data value array inputs must have identical size")

    # get overall number of knots
    n = t.size-1

    # create output
    s = numpy.zeros(numpy.shape(x))

    # evaluate spline for each entry in x
    for j in range(x.size):

        # determine spline interval for this x value
        if (x[j] < t[0]):
            i = 0
        elif (x[j] > t[n]):
            i = n-1
        else:
            for i in range(n):
                if ( (x[j] >= t[i]) and (x[j] < t[i+1]) ):
                    break

        # set subinterval width
        h = t[i+1] - t[i]

        # evaluate spline
        s[j] = ( z[i]/(6.0*h)*(t[i+1]-x[j])**3 +
                 z[i+1]/(6.0*h)*(x[j]-t[i])**3 +
                 (y[i+1]/h - z[i+1]*h/6.0)*(x[j]-t[i]) +
                 (y[i]/h - z[i]*h/6.0)*(t[i+1]-x[j]) )

    return s


import numpy as np

verts = [
    [2.48262054,  0.,          0.], [2.18126921,  0.74883066,  0.],
    [1.27680251,  0.99377529,  0.], [0.70457323,  1.07842961,  0.],
    [0.44261043,  1.74782907,  0.], [-0.13097626,  1.5806484,   0.],
    [-0.94757905,  2.16026265,  0.], [-0.93380734,  1.01438518,  0.],
    [-1.52347523,  0.82446356,  0.], [-1.92350252,  0.3209758,  0.],
    [-2.57691846, -0.43001164,  0.], [-1.57387206, -0.85173697,  0.],
    [-0.90413648, -0.98215403,  0.], [-0.92463881, -2.10796417,  0.],
    [-0.16693189, -2.01456837,  0.], [0.31202013, -1.23213965,  0.],
    [1.43075202, -2.18992899,  0.], [2.18188065, -1.69822587,  0.],
    [2.28210863, -0.78344888,  0.]
]

def make_combos(nsize):
    """
    finds all unique pairs of index combinations
      input  num = 5
      output = [[0 1] [0 2] [0 3] [0 4] [1 2] [1 3] [1 4] [2 3] [2 4] [3 4]]
    """
    A1, B1 = np.triu_indices(nsize-1, 0)
    COMBOS = np.ravel(np.column_stack((A1, B1))).reshape((-1, 2)) + [0, 1]
    return COMBOS

def find_distances(VERTS, combos):
    """
    input  verts = np.array of verts
    input  combos = np.array of (all possible) combos between verts, passed as indices of verts
    output  np.array of linear distances between all combos
    """
    column_one = combos[:,0]
    column_two = combos[:,1]
    V1 = np.take(VERTS, column_one, axis=0)
    V2 = np.take(VERTS, column_two, axis=0)
    return np.linalg.norm(V1 - V2, axis=1)

def get_combos_within_distance(combos, distances, dist):
    too_close = np.where(distances < dist)
    return np.take(combos, too_close, axis=0)

VERTS = np.array(verts)
num_verts = VERTS.shape[0]
combos = make_combos(num_verts)
distances = find_distances(VERTS, combos)

# print(distances)
vertex_combos_too_close = get_combos_within_distance(combos, distances, 1.0)
print(vertex_combos_too_close)
# > [[[ 0  1]  [ 0 18]  [ 1  2]  [ 2  3]  [ 3  4]  [ 3  5]  [ 4  5]
# [ 5  7]  [ 7  8]  [ 8  9]  [ 9 10]  [11 12]  [13 14]  [14 15]  [16 17]  [17 18]]]

# xy, x_ind, y_ind = np.intersect1d(x, y, return_indices=True)


"""
Numerical Integration
---------------------

Here is an introduction to Numerical Integration with Python!
"""
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

###############################################################################
# Let's look at a simple function:
#
# .. math::
#
#     g(x) = \sin(x)
#
# which we know the analytical integral as:
#
# .. math::
#
#     G(x) = - \cos(x)
#
# and let's integrate that from 0 to :math:`\frac{5\pi}{4}`. Analytically, this is:
#
# .. math::
#
#     \int_0^{\frac{5\pi}{4}} g(x) \, dx = - \cos(x) |_0^{\frac{5\pi}{4}} = - \cos(\frac{5\pi}{4}) + \cos(0) = \frac{\sqrt{2}}{2} + 1 = 1.7071

np.sqrt(2) / 2 + 1

###############################################################################
# We can visualize this as the area under the curve - which is all a numerical
# integral is; an approximation of the area under the curve.
# To do a numerical integration, we simply discretize our x space and then
# evaluate our function. Once we have that, we can sum the evaluation and
# multiply by the discretization factor to yield the effective area under the
# curve.
#
# So let's discretize :math:`g(x)` into several rectangles which we can use to
# solve for the area under this curve.

g = lambda x: np.sin(x)
a, b = 0, 5 * np.pi / 4

y = np.linspace(a, b)

dy = np.pi / 16
yy = np.arange(a, b, dy)

def plot_rects(xx, f, delta):
    for v in xx:
        value = f(v)
        if value >= 0.0:
            loc = 0.0
            h = value
        else:
            loc = value
            h = 0 - value
        r = mpl.patches.Rectangle(
            (v - delta / 2.0, loc), width=delta, height=h, edgecolor="k", linewidth=1
        )
        plt.gca().add_patch(r)
    return

def plot_g():
    plt.plot(y, g(y))
    plt.plot(yy, g(yy), "ro")

    plot_rects(yy, g, dy)

    plt.xlabel("$x$")
    plt.ylabel("$g(x)$")

plot_g()
plt.show()

###############################################################################
# So now we can sum the areas of all of those rectangles to get an
# approximation of the area under the curve (the integral)
np.sum(g(yy - (dy / 2)) * dy)

# That's not too bad considering our rectangles do not perfectly follow the
# curve. Let's now try choosing a :math:`\Delta x` value that will increase the
# sampling creating finer spaced rectangles to improve our approximation.

dy = np.pi / 32
yy = np.arange(a, b, dy)

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plot_g()
plt.title("Area = {:.3f}".format(np.sum(g(yy) * dy)))

plt.subplot(1, 2, 2)

dy = np.pi / 128
yy = np.arange(a, b, dy)

plot_g()
plt.title("Area = {:.3f}".format(np.sum(g(yy) * dy)))

plt.tight_layout()
plt.show()

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

np.sum(g(yy) * dy)

###############################################################################
# Now that's much better! as we decrease the :math:`\Delta x` value, we see the
# apprximationm start to converge on the analtical answer for the integral.
# The rectangles appear to be consistently overestimating the integral.
#
# Integrating a Complex Function
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Now let's apply that concept to numerically integrate a more complex function
# such as:
#
# .. math::
#
#     f(x) = \sin(\frac{1}{x})
#
# from 0 to :math:`2\pi`. This allows us to really showcase where numerical
# integration benefits as I don't even know where to begin when it comes to
# analytically integrating that.

f = lambda x: np.sin(1 / x)

###############################################################################
# To do so, we'll need to define a range of x values on which to evaluate the
# function. Let's try out a few different sets of x values to plot the values
# of :math:`f(x)` to gain insight into how this function behaves.
#
# First, let's see what happens when we plot this function at linear intervals
# between 0 and :math:`2\pi` (note that we cannont have a division by zero, so our
# lower boound must be some small value, not 0: we'll use :math:`10^{-9}`.

num = 100000
zero = 1e-9

# Create an array of x values to evaluate
x = np.linspace(zero, 2 * np.pi, num)

# Evaluate and plot the values
plt.plot(x, f(x))
plt.xlabel("$x$")
plt.ylabel("$f(x)$")
plt.show()

###############################################################################
# Huh, that's weird. The function behaves quite different from a typical :math:`\sin`
# function and we appear to see some sort of decay - let's look into that a bit
# more. *At what x value are we seeing a change from oscillatory behavior to
# a decay?*
#
# From visual inspection, it looks like some value above :math:`\frac{1}{\pi}`
# (0.318). Let's reevaluate the function from that x value onward.
# Then find the maximum and see what interval of :math:`\pi` is causing the change
# in behavior.

subset = np.linspace(0.3, 2 * np.pi, num)
subset[np.argmax(f(subset))] * np.pi

###############################################################################
# Aha! It appears that the x value of :math:`\frac{2}{\pi}` marks the start of
# decay. This makes sense because :math:`\sin(1/2/\pi) = \sin(\pi/2) = 1`

plt.plot(x, f(x), label="$f(x)$")
plt.plot(2.0 / np.pi, f(2.0 / np.pi), "ro", label=r"$x=\frac{2}{\pi}$")
plt.legend()
plt.xlabel("$x$")
plt.ylabel("$f(x)$")
plt.show()

###############################################################################
# So now we know that have a speration point in our function - this might come
# in handy later. as we may have to treat those two regions of the function
# separately when numerically integrating.
#
# Now, let's explore the jumbled up portion of the plot for very low x values.
# There appears to be some high frequency oscillation between -1 and 1, but we
# can't really tell. To do this, let's create an exponentially varying x
# space to evaluate our function which will bring out finer sampling for lower
# values and see how the function behaves as x approaches zero.

x = np.geomspace(1e-2, 2 * np.pi, num)

plt.plot(x, f(x))
plt.gca().set_xscale("log")
plt.xlabel("$x$")
plt.ylabel("$f(x)$")
plt.show()

###############################################################################
# Now that's cool! :math:`f(x)` appears to have increasing frequency as x
# approaches zero. This is quite different from a normal :math:`\sin` function where
# we don't see any frequency change at all. This frequency variation will
# definitely pose a challenge for our numerical integration as we will need to
# make sure we properly sample this signal without aliasing.
#
# Let's see if we can come up with an equation for capturing that frequency
# variation as something we can use to build out our values that we will use
# for the numerical integration.
#
# First, let's evaluate our function between integer changes in :math:`\pi` to see
# how :math:`f(x)` oscilates between -1 and 1. We know from above that the last
# value of 1 occurred at :math:`\frac{2}{\pi}`, so let's use decreasing values from
# there.

f(3.0 / np.pi)

###############################################################################
f(3.0 / (2.0 * np.pi))

###############################################################################
f(2.0 / (3 * np.pi))

###############################################################################
f(2.0 / (4 * np.pi))

###############################################################################
f(2.0 / (5 * np.pi))

###############################################################################
# So this is interesting. I'm seeing a weird pattern where :math:`\frac{2}{n \pi}`
# appears to oscillate :math:`f(x)` between -1 and 1. Let's test this out.

xx = lambda n: 2 / (n * np.pi)
nn = 65
n = np.arange(1, nn)
xn = xx(n)

plt.plot(xn, f(xn), "ro")
plt.plot(x, f(x))
plt.gca().set_xscale("log")
plt.xlabel("$x$")
plt.ylabel("$f(x)$")
plt.show()

###############################################################################
# It appears that the equation :math:`\frac{2}{n \pi}` captures the oscilatory
# behavior of :math:`f(x)` quite well! We see that it marks the minimums, maximums,
# and zero-cross points for every period of :math:`f(x)`. This is just what we need
# to effectively integrate :math:`f(x)`.
#
# To start integrating this function, we simply need to sample several times
# between values of :math:`n`. Doing so is as simple as bumping up the number of
# setting a step size in ``numpy``'s ``arange`` method. Let's use a step size of
# 0.25 as that will give us 4 values between each cross-over and the min/max
# point. Let's see if that gives us a visually pleasing approximation of our
# function.

def plot_f():
    plt.gca().set_xscale("log")
    plt.plot(x, f(x))
    plt.plot(xn, f(xn), "ro")

    for i, v in enumerate(xn):
        if i == len(xn) - 1:
            continue
        value = f(v)
        if value >= 0.0:
            loc = 0.0
            h = value
        else:
            loc = value
            h = 0 - value
        delta = abs(xn[i] - xn[i + 1])
        r = mpl.patches.Rectangle(
            (v - delta / 2, loc), width=delta, height=h, edgecolor="k", linewidth=1
        )
        plt.gca().add_patch(r)
    plt.xlabel("$x$")
    plt.ylabel("$f(x)$")
    return

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

dx = 0.25
n = np.arange(1, nn, step=dx)
xn = xx(n)

plot_f()
plt.show()

###############################################################################
# That looks decent, but I think we can do better. Let's try 0.1 for 10 samples
# between the cross overs and the min/max

dn = 0.1
# 1/np.pi**2
n = np.arange(1, nn, step=dn)
xn = xx(n)

plot_f()
plt.show()

###############################################################################
# Now that looks pretty darn good! How about we try integrating :math:`f(x)` now!
# To do so, we simply take our values and multiply by the :math:`\Delta x` value
# (the rectangle widths). It's important to note here that :math:`\Delta x` is not a
# constant this time, so well need to compute an array of all of our
# :math:`\Delta x` values first.

dx = np.pi / (128 * 4)
x = np.arange(2 / np.pi, 2 * np.pi, step=dx)
right = np.sum(f(x) * dx)
right

###############################################################################
# The right side there is pretty good
# (`check with wolfram alpha <https://www.wolframalpha.com/input/?i=integrate+sin+1%2Fx+dx+from+x%3D2%2Fpi+to+2pi>`_)

def compute_left(dn, nn=1000):
    n = np.arange(1, nn, step=dn)
    xn = xx(n)
    dx = np.abs(np.diff(xn))
    return np.sum(f(xn)[:-1] * dx)

left = compute_left(dn)
left

###############################################################################
# Well that's not very good as we know the true value for the left side to be
# 0.164619 `from Wolfram Alpha <https://www.wolframalpha.com/input/?i=integrate+sin+1%2Fx+dx+from+x%3D0+to+2%2Fpi+>`_.
# Let's see if increasing the discretization helps.

left = compute_left(0.0001)
left

###############################################################################
left + right

###############################################################################
# The full integral is known to be 2.26277 from `Wolfram Alpha <https://www.wolframalpha.com/input/?i=integrate+sin+1%2Fx+dx+from+x%3D0+to+2pi>`_
# so that's not too bad of an approximation!
#
# And there you have it! The integral of :math:`\sin(\frac{1}{x})` has been
# numerically integrated!


import numpy as np
import matplotlib.pyplot as plt

from featureNormalize import featureNormalize
from computeCostMulti import computeCostMulti
from gradientDescentMulti import gradientDescentMulti
from normalEqn import normalEqn

# ================ Part 1: Feature Normalization ================
print('Loading data ...')
data = np.loadtxt('ex1data2.txt', delimiter=',')
X = data[:, [0, 1]]
y = data[:, [2]]
m = y.size

print('First 10 examples from the dataset:')
for i in range(10):
    print('x = [%d %d], y = %d' % (X[i, 0], X[i, 1], y[i]))

print('Normalizing Features ...\n')
X, mu, sigma = featureNormalize(X)
X = np.hstack((np.ones((m, 1)), X))

# ================ Part 2: Gradient Descent ================
print('Running gradient descent ...')
alpha = 0.01
iters = 400
theta = np.zeros((X.shape[1], 1))
theta, J_history = gradientDescentMulti(X, y, theta, alpha, iters)

# Plot the convergence graph
plt.plot(range(1, len(J_history) + 1), J_history)
plt.xlabel('Number of iterations')
plt.ylabel('Cost J')
plt.show()

print('Theta computed from gradient descent:')
print(theta)

# Estimate the price of a 1650 sq-ft, 3 br house
price = np.dot([1, (1650 - mu[0]) / sigma[0], (3 - mu[1]) / sigma[1]], theta)
print('Predicted price of a 1650 sq-ft, 3 br house (using gradient descent): $%f\n' % price)

# ================ Part 3: Normal Equations ================
print('Solving with normal equations...')
X = np.hstack((np.ones((m, 1)), data[:, [0, 1]]))
y = data[:, [2]]
theta = normalEqn(X, y)

print('Theta computed from the normal equations:')
print(theta)

# Estimate the price of a 1650 sq-ft, 3 br house
price = np.dot([1, 1650, 3], theta)
print('Predicted price of a 1650 sq-ft, 3 br house (using normal equations): $%f' % price)


from IPython.display import Image
import numpy as np
from scipy.stats import pearsonr

def answer_one(your_answer):
    '''
    your answer should be a float identifying the correlation between the user_1 and user_2 vectors
    '''
    if your_answer == 0.9658597610592513:
        return Image(filename='../../images/you_right.png')

    else:
        print("Sorry, that's not correct.  Notice, a tuple of values is returned.  The first value is the correlation coefficient, while the second is a p-value for a hypothesis test testing if the correlation coefficient is different than zero.")

def answer_two(your_answer):
    '''
    your answer should be a letter
    a to d
    '''
    if your_answer == "user_1 and user_2 have a positive, linear relationship":
        print("That's right! The value of the correlation is quite close to 1 meaning there is a positive, linear relationship.")
        return Image(filename='../../images/yup.png')

    else:
        print("Sorry, that's not correct.  Try again!")

def answer_three(your_answer):
    '''
    your answer should be a float identifying the cosine similarity between user_1 and user_2
    '''
    if your_answer == 0.9903238090120656:
        return Image(filename='../../images/good_job.png')
    else:
        print("Sorry the number is not correct. Please try again!")

def answer_four(your_answer):
    '''
    your answer should be a letter a-e
    '''
    if your_answer == "Subtract the standard deviation from each vector":
        return Image(filename='../../images/good_job.png')
    else:
        print("Sorry this is not correct.  Please try again!")

def answer_five(your_answer):
    '''
    your answer should be a 'float' identifying the correlation coefficient between user_1 and user_2 when a nan is present
    '''
    if np.isnan(your_answer):
        print("That's right!  The correlation when a nan is present is also nan.")
        return Image(filename='../../images/good_job.png')

    else:
        print("Sorry, that's not correct.  Try again!")

def answer_six(your_answer):
    '''
    your answer should be a 'float' identifying the correlation coefficient between user_1 and user_2 when a nan is present
    '''
    if your_answer == 0.9628704197969145:
        print("That's right!  It looks like you successfully removed the nan and computed the correlation coefficient.")
        return Image(filename='../../images/good_job.png')

    else:
        print("Sorry, that's not correct.  Try again!")

def compute_correl_2(vec1, vec2):
    '''
    inputs:
    vec1 - np.array
    vec2 - np.array

    return:
    pearson correlation between two vectors where indices with a nan in
    either vector are removed from both vectors
    '''
    indices = np.logical_not(np.logical_or(np.isnan(vec1), np.isnan(vec2)))
    vec1, vec2 = vec1[indices], vec2[indices]

    return pearsonr(vec1, vec2)[0]

def answer_seven(your_answer):
    '''
    your answer should be a 'float' identifying the jaccard similarity between x and y
    '''
    if your_answer == 0.6:
        print("That's right!  It looks like you successfully computed the jaccard similarity!")
        return Image(filename='../../images/good_job.png')

    else:
        print("Sorry, that's not correct.  Try again!")

Part 3 - Recommendation Systems/notebooks/data_solution_part1.py


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import qr
import math

DEF_SAMPLE_SIZE = 100000
DEF_TOSS_SIZE = 1000

COLORS = ['red', 'yellow', 'blue', 'green', 'black']
data = np.random.binomial(1, 0.25, (DEF_SAMPLE_SIZE, DEF_TOSS_SIZE))
epsilon = [0.5, 0.25, 0.1, 0.01, 0.001]

def plot_mean_by_m():
    """
    Does as follows:
    1) Takes first five rows, representing first five sequences of 1000 coin tosses
    2) For each row calculates mean as a function of m tosses
    3) Plots all values on the same graph with a different color for each sequence
    :return:
    """
    temp = data[:5] # Extract first five sequences
    m_values = [m+1 for m in range(DEF_TOSS_SIZE)]
    for i in range(len(temp)):
        y_row = []
        cur_sum = 0 # Sum of first m tosses
        for j in range(DEF_TOSS_SIZE):
            cur_sum += temp[i][j]
            y_row.append(cur_sum / float(j+1)) # Add mean
        plt.plot(m_values, y_row, color = COLORS[i], label = "Row {}".format(i))
    plt.title("Mean estimate")
    plt.legend()
    plt.show()

def plot_chebyshev_hoeffding_percentage_by_m():
    """
    Plots Chebyshev and Hoeffding upper bounds
    :return:
    """
    m_values = np.arange(1, DEF_TOSS_SIZE+1)

    for eps in epsilon:
        data_cumulative = np.array([np.absolute(np.cumsum(data[i])/np.arange(1, DEF_TOSS_SIZE+1)-0.25)>=eps for i in range(len(data))])
        # data_cumulative = np.absolute(np.divide(np.cumsum(data, axis=1), np.tile(np.arange(1,DEF_TOSS_SIZE+1), (DEF_SAMPLE_SIZE,1)))-0.25)

        y_row1 = []
        y_row2 = []
        y_row3 = []
        for j in range(1, DEF_TOSS_SIZE+1):
            y_row1.append(1/(4*j*(eps**2))) # Chebyshev
            y_row2.append(2*(math.e**(-2*j*(eps**2)))) # Hoeffding

            # Percentage
            amount = np.where(data_cumulative[:,j-1]==True)[0].size
            # amount = np.where(data_cumulative[:,j-1]>=eps)[0].size
            y_row3.append(amount/float(DEF_SAMPLE_SIZE))

        # Set up plot
        plt.title("epsilon = " + str(eps))
        plt.plot(m_values, y_row1, color=COLORS[0], label="Chebyshev")
        plt.plot(m_values, y_row2, color=COLORS[2], label="Hoeffding")
        plt.plot(m_values, y_row3, color=COLORS[1], label="Percentage")
        plt.legend()
        plt.ylim(0, 2)
        plt.show()

if __name__ == '__main__':
    # a
    plot_mean_by_m()

    # b and c
    plot_chebyshev_hoeffding_percentage_by_m()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Oct 21 09:57:43 2019

@author: amandaash
"""

import numpy as np
import matplotlib.pyplot as plt

p = 2
v = 1
x = 0
m = 10
time_step = 0.0001
k = 3
t0 = 0
tf = 10

"""
x_val = []
v_val = []

time_array = np.arange(t0,tf, time_step)

for n in time_array:

    v1 = v + (time_step/m)*(-k*x**(p-1))
    x1 = x + time_step*v

    x_val.append(x1)
    v_val.append(v1)

    v1 = v
    x1 = x

plt.plot(time_array, x_val)
plt.show()
plt.plot(time_array, v_val)
plt.show()
"""

def harmonic_oscillator(p,k,v0,x0,m,time_step,t0,tf):
    v = v0
    x = x0
    x_val = []
    v_val = []
    time_array = np.arange(t0,tf, time_step)

    for n in time_array:

        vf = v + (time_step/m)*(-k*x**(p-1))
        xf = x + time_step*v

        x_val.append(xf)
        v_val.append(vf)

        x = xf
        v = vf

    return x_val, v_val, time_array

#P_val = np.arange(2,8,2)
P_val = np.array([2,6,10])
fig1 = plt.figure()
ax1 = fig1.add_subplot(1, 1, 1)
fig2 = plt.figure()
ax2 = fig2.add_subplot(1,1,1)

for P_value in P_val:
       x_P, v_P, t_P= harmonic_oscillator(P_value, 10, 0, 1, 1, 0.0001, 0, 10)
       ax1.plot(x_P, t_P, label = "P = {0}".format(P_value))
       ax2.plot(v_P, t_P, label = "P = {0}".format(P_value))

ax1.set_xlabel('distance')
ax1.set_ylabel('time')
ax1.legend()
fig1.savefig('spring_dt.pdf')
fig1.show()

ax2.set_xlabel('velocity')
ax2.set_ylabel('time')
ax2.legend()
fig2.savefig('spring_vt.pdf')
fig2.show()

#amplitude - frequency things:
fig3 = plt.figure()
ax3 = fig3.add_subplot(1,1,1)
x_ic = np.arange(0.5,2.0,0.5)
for amplitude in x_ic:
    x_a, v_a, t_a = harmonic_oscillator(6, 10, 0, amplitude, 1, 0.0001, 0, 10)
    ax3.plot(x_a, t_a, label = '$x_0$ = {0}'.format(amplitude))
ax3.set_title('P = 6, non-harmonic oscillator varying $x_0$')
ax3.set_xlabel('x')
ax3.set_ylabel('t')
ax3.legend()
fig3.savefig('non_harmonic_amplitude.pdf')
fig3.show()

#Going between the RK2 method and the Euler method from exercise 19, I see no
#difference between the two methods in either the position vs. time or velocity vs. time for the oscillator.


from numpy.linalg import norm
from numpy import dot

def cosineDistance(a, b):
    return 1 - (dot(a, b) / (norm(a) * norm(b)))

def euclideanDistance(a, b):
    return norm(a-b)


import numpy as np
"""
MECHANICAL ENGINEERING POST-GRADUATE PROGRAM
UNIVERSIDADE FEDERAL DO ABC - SANTO ANDRÉ, BRASIL

NOME: RAFAEL COSTA FERNANDES
RA: 21201920754
E−MAIL: COSTA.FERNANDES@UFABC.EDU.BR

DESCRIPTION:
    Quaternion and euler utility algorithms.
    Converts euler to quaternions
    Converts quaternions to euler
    Computes the quaternions derivative (Useful for time integration of quaternions)
    Converts quaternions to rotatio matrix
"""
def euler_quat(ang):
    #ROTACAO 3-2-1
    phi = ang[0]
    theta = ang[1]
    psi = ang[2]

    cp = np.cos(phi/2)
    sp = np.sin(phi/2)
    ct = np.cos(theta/2)
    st = np.sin(theta/2)
    cps = np.cos(psi/2)
    sps = np.sin(psi/2)

    q0 = cp*ct*cps+sp*st*sps
    q1 = sp*ct*cps-cp*st*sps
    q2 = cp*st*cps+sp*ct*sps
    q3 = cp*ct*sps-sp*st*cps
    q = np.array([[q0, q1, q2, q3]]).T
    q = q/np.linalg.norm(q)
    return q

def quat_euler(q):
    phi = np.arctan2(2*(q[0]*q[1]+q[2]*q[3]), 1-2*(q[1]**2+q[2]**2))
    theta = np.arcsin(2*(q[0]*q[2]-q[3]*q[1]))
    psi = np.arctan2(2*(q[0]*q[3]+q[1]*q[2]), 1-2*(q[2]**2+q[3]**2))
    phi = phi[0]
    theta = theta[0]
    psi = psi[0]
    if any(np.isnan([phi, theta, psi])):
        print('Divergencia na conversao Quaternion - Euler')
    return np.array([phi, theta, psi])

def deriv_quat(w, q):
    w = w.flatten()
    q.reshape((4,1))
    wx = w[0]
    wy = w[1]
    wz = w[2]
    omega = np.array([[0, -wx, -wy, -wz],
                      [wx, 0, wz, -wy],
                      [wy, -wz, 0, wx],
                      [wz, wy, -wx, 0]])
    dq = 1/2*np.dot(omega,q).flatten()
    return dq

def quat_rot_mat(q):
    q = q.flatten()
    a = q[0]
    b = q[1]
    c = q[2]
    d = q[3]
    R = np.array([[a**2+b**2-c**2-d**2, 2*b*c-2*a*d, 2*b*d+2*a*c],
                  [2*b*c+2*a*d, a**2-b**2+c**2-d**2, 2*c*d-2*a*b],
                  [2*b*d-2*a*c, 2*c*d+2*a*b, a**2-b**2-c**2+d**2]])
    return R


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

class ActiveLearning:

    def __init__(self, X_train, X_test, Y_train, Y_test, lambda_param=1.0, noise_param=1.0):

        self.X_train = X_train;
        self.X_test = X_test;

        self.Y_train = Y_train;
        self.Y_test = Y_test;

        self.lambda_param = lambda_param;
        self.noise_param = noise_param;

    def compute_posterior_distribution(self, X, Y):

        # used to compute the posterior distribution for the MAP inference
        # This represents the interpretation of RR from a Bayesian perspective

        data_correlation = np.matmul(np.transpose(X), X);
        data_label_correlation = np.matmul(np.transpose(X), Y);

        tmp = self.lambda_param * self.noise_param * np.identity(data_correlation.shape[0]);
        tmp2 = self.lambda_param * np.identity(data_correlation.shape[0]);

        # compute the mean and the covariance of the data
        mean = np.matmul(la.inv(tmp + data_correlation), data_label_correlation);
        covariance = la.inv(tmp2 + data_correlation/self.noise_param);

        return (mean, covariance);

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

        # used to compute the predictive distribution for the Regression framework
        # This represents the prediction on a new data point with a measure of uncertainity in the prediction

        # x is a single data point as represented by a lower case representation

        prediction = np.dot(x, mean);

        tmp = np.matmul(covariance, x);
        prediction_uncertainity = self.noise_param + np.dot(x,tmp);

        return (prediction, prediction_uncertainity);

    def learn_posterior(self, num_epochs=10):

        # employs the active learning iterative technique to learn the posterior efficiently
        # This reduces the entropy of the posterior distribution that we learn over the dataset
        # This type of learning can also be used for semi supervised learning problem

        mean , covariance = 0, 0;

        for i in range(num_epochs):

            # estimate the posterior distribution over the entire training data
            mean , covariance = self.compute_posterior_distribution(self.X_train, self.Y_train);

            # compute the entropy of the posterior
            entropy = np.log(2 * math.pi * math.e * la.det(covariance));
            print(entropy);

            max_sigma = 0.0;
            max_index = -1;
            max_prediction = 0.0;

            # estimate the predictive distribution over the entire testing dataset
            for i in range(len(self.X_test)):
                current_sample = self.X_test[i];
                prediction , prediction_uncertainity = self.compute_predictive_distribution(current_sample,
                                                                                mean,
                                                                                covariance);
                if(prediction_uncertainity > max_sigma):
                    max_index = i;
                    max_sigma = prediction_uncertainity;
                    max_prediction = prediction;

            # append the data point to the training set
            self.X_train = np.append(self.X_train,
                                    np.reshape(self.X_test[max_index],newshape=(-1,self.X_test[max_index].shape[0])),
                                    axis=0);
            self.Y_train = np.append(self.Y_train, max_prediction);

        return (mean, covariance);


# we are having batches because of parallelization as it increases efficiency and speed of our program
# batches inputs are mainly preferrable in GPU due to it's high amount of core
# batch size of 32 is pretty common

import numpy as np

# INPUTS
inputs = [[1 , 2 , 3 , 2.5],
          [2.0 , 5.0 , -1.0 , 2.0],
          [-1.5 , 2.7 , 3.3 , -0.8]]

# WEIGHTS
weights = [[0.2 , 0.8 , -0.5 , 1.0],
        [0.5 , -0.91 , 0.26 , -0.5],
        [-0.26 , -0.27 , 0.17 , 0.87]]

# BIASES
biases = [2, 3, 0.5]

# OUTPUT
layer1_output = np.dot(inputs , np.array(weights).T) + biases

# Now this output from layer 1 will become input for layer 2

weights2 = [[0.1 , -0.14 , 0.5],
            [-0.5 , 0.12 , -0.33],
            [-0.44 , 0.73 , -0.13]]

biases2 = [-1 , 2 , -0.5]

layer2_output  = np.dot(layer1_output , np.array(weights2).T) + biases2

print(layer2_output)


from math import sqrt, log
from numpy import *

# Computes zk such that P(Z<z) = p.
# This function uses a numerical approximation from : Statistical Computing,
# by W.J. Kennedy, Jr. and James E. Gentle, 1980, p. 95.
# @return z such that P(Z<z)=p
# @param p is a real number between 0 and 1 (probability)

def inverse_normal_score(prob, mean = 0.0, var = 1.0):
	Pconst = [ -0.322232431088,-1.0,-0.342242088547, -0.0204231210245,-0.0000453642210148 ];
	Qconst = [ 0.0993484626060,0.588581570495,0.531103462366, 0.103537752850,0.0038560700634 ];

	if prob >= 1.0:
		return 3
	elif prob <= 0.0:
		return -3

	tmp_prob=prob;
	if(prob>0.5):
		tmp_prob=1-prob;

	y = sqrt(log(1.0/(tmp_prob*tmp_prob))) # logarithm is natural
	num=(((y*Pconst[4]+Pconst[3])*y+Pconst[2])*y+Pconst[1])*y+Pconst[0]
	denom=(((y*Qconst[4]+Qconst[3])*y+Qconst[2])*y+Qconst[1])*y+Qconst[0]

	result = y+num/denom

	if (prob==tmp_prob):
		result = -result

	R = result*sqrt(var) + mean
	return R

def normal_score(prob):
	a1 = 0.4361836
	a2 = -0.1201676
	a3 = 0.9372980
	p = 0.33267
	z = exp(-(prob**2)/2)/(sqrt(2*pi))
	t = 1./(1 + p*prob)
	P_x = 1 - z*(a1*t + a2*(t**2) + a3*(t**3))
	return P_x

def gaussian_cdf(value, mean = 0.0, var = 1.0):
	p = 0.2316419
	b = array([0.319381530, -0.356563782, 1.781477937, -1.821255978, 1.330274429])
	sx = (value - mean) / sqrt(var)

	neg = sx < 0

	if (neg == False):
		sx = - sx

	t = 1 / (1 + p * sx)

	zx = 1 / sqrt(2 * 3.14159265358) * exp(- sx * sx / 2)
	result = 1 - zx * ((((b[4] * t + b[3]) * t + b[2]) * t + b[1]) * t + b[0]) * t

	if (neg == False):
		result = 1 - result

	return result

def cdf_transform(array_data, undefined_value):
	array_copy = copy(array_data)
	value = 0.0
	props = array([])
	values = array([])
	defined_values_count = float(sum(array_copy != undefined_value))
	for i in xrange (array_copy.shape[0]):
		for j in xrange (array_copy.shape[1]):
			if(array_copy[i,j] != undefined_value):
				value += float(sum(array_copy == array_copy[i,j])) / defined_values_count
				props = append(props, value)
				values = append(values, array_copy[i,j])
				array_data[i,j] = inverse_normal_score(value)
	return props, values

def back_cdf_transform(property, props, values, undefined_value):
	for i in xrange(property.shape[0]):
		for j in xrange(property.shape[1]):
			for k in xrange(property.shape[2]):
				if(property[i, j, k] != undefined_value):
					property[i, j, k] = interp(gaussian_cdf(property[i,j,k]), props, values)


import numpy as np
from scipy import stats

# Fonction test des moyennes et variances
def test_moyenne(x, y, pval_only=False):
    """
    @return: str
    @type x: np.array
    @type y: np.array
    """
    # Normality test
    sv_x, sp_x = stats.shapiro(x)
    sv_y, sp_y = stats.shapiro(y)

    if sp_x < 0.05 or sp_y < 0.05:
        var = "H1"
        test_n = "Au moins une distribution ne suit pas une loi normale"

    else:
        var = "H0"
        test_n = ""

    # Variance test
    if var == 'H0':
        vv, vp = stats.bartlett(x, y)
        if vp < 0.05:
            test_v = f"Les écart-types sont significativement différents:
{np.std(x):.3f} et {np.std(y):.3f}, p= {vp:.4f}"
        else:
            test_v = f"Les écart-types ne sont pas significativement différents:
{np.std(x):.3f} et {np.std(y):.3f}, p= {vp:.4f}"

    else:
        vv, vp = stats.levene(x, y)
        if vp < 0.05:
            test_v = f"Les écart-types sont significativement différents:
{np.std(x):.3f} et {np.std(y):.3f}, p= {vp:.4f}"
        else:
            test_v = f"Les écart-types ne sont pas significativement différents:
{np.std(x):.3f} et {np.std(y):.3f}, p= {vp:.4f}"

    # mean test
    if vp >= 0.05:
        tv, tp = stats.ttest_ind(x, y, equal_var=True)
    else:
        tv, tp = stats.ttest_ind(x, y, equal_var=False)  # Welch

    if tp >= 0.05:
        test_m = f"Les moyennes ne sont pas significativement différentes:
{np.mean(x):.3f} et {np.mean(y):.3f}, p= {tp:.4f}"
    else:
        test_m = f"Les moyennes sont significativement différentes:
{np.mean(x):.3f} et {np.mean(y):.3f}, p= {tp:.4f}"

    if pval_only == False :
        return test_n + '
' + test_v + '
' + test_m

    else :
        return {"Moyenne x" :np.rint(np.mean(x)), "Var x" :np.rint(np.std(x)),
                "Moyenne y" :np.rint(np.mean(y)), "Var y" :np.rint(np.std(y)),
                "p-value Moyenne" :np.around(tp,2), "p-value Var" :np.around(vp,2)}


from sklearn.metrics.pairwise import rbf_kernel
from .basis_functions import BasisFunctions
from .utils import rbf
from numpy import array, atleast_2d, tile, diag, reshape

class RBF(BasisFunctions):
    """
    Implements Radial Basis Functions (RBD) as a basis function
    """

    def __init__(self, rbf_centers, n, gamma=1., type='gaussian'):
        """__init__ [summary]

        Arguments:
            rbf_centers {numpy array [Ns,Nc]} -- points for the RBFs
            n {integer} -- Ns, number of states

        Keyword Arguments:
            gamma {float} -- gamma value for gaussian RBF (default: {1.})
            type {str} -- RBF Type (default: {'gaussian'})
        """
        self.n = n
        self.Nlift = rbf_centers.shape[1]
        self.rbf_centers = rbf_centers
        self.gamma = gamma
        self.type = type
        self.Lambda = None
        self.basis = None

    def lift(self, q, q_d):
        """lift Lift the state using the basis function

        Arguments:
            q {numpy array [Ns,Nt]} -- state
            q_d {numpy array [Nt,Nt]} -- desired state

        Returns:
            numpy array [Nz,Nt] -- lifted state
        """
        if q.ndim == 1:
            q = reshape(q,(q.shape[0],1))

        return atleast_2d(self.basis(q, q_d).squeeze())

    def construct_basis(self):
        if self.type == 'gaussian':
            self.basis = lambda q, q_t: rbf(q, self.rbf_centers, eps=self.gamma)
        else:
            raise Exception('RBF kernels other than Gaussian not implemented')