"""
Function to find the gradient descent on a set of data points divided into 4 classes
Algorithm:

a(k + 1) = a(k) − η(k)∇J(a(k))
a is weight vector
η is learning rate.
J is the hypothesis function
∇ - gradient
k is the iteration or the sample number
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

def grab_data(file_path):
    data_df = pd.read_csv(file_path)
    data_features = np.zeros([10,8])
    i=0
    for col in data_df.columns:
        data_features[:,i] = data_df[col]
        i+=1
    train_features, labels = create_dataset(data_features)

    return [train_features, labels]

def create_dataset(data):

    cls_1 = data[:, 0:2]
    cls_2 = data[:, 2:4]
    features = np.concatenate((cls_1,cls_2))
    labels = np.array([1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0])
    return features, labels

def split_data():
    pass

def optimize_weight_vector(*, method=0, features=None):
    operation(method=method, epochs=60000, tr_label=1, train_set=features, lr=0.0001)

def sigmoid_activation(x):
    # compute and return the sigmoid activation value for a
    # given input value
    return 1.0 / (1 + np.exp(-x))

def operation(*,method=0,epochs=600, tr_label=1, train_set=None, lr=0.1):
    """
    a(k + 1) = a(k) − η(k)∇J(a(k))
    ∇J(a) = y(k)

    :param epochs:
    :param train_set:
    :param tr_label:
    :param lr:
    :return:
    """
    loss_history = []
    training_data, labels = train_set[0], train_set[1]

    training_data_points = augument_train_vector(training_data)

    # initialize weights
    weights = np.random.uniform(size=(training_data_points.shape[1],))

    for each_epoch in np.arange(0, epochs):
        pred = find_hypothesis(training_data_points.dot(weights))
        # pred = training_data_points.dot(weights)
        error = pred - labels
        loss = np.sum(error ** 2)
        loss_history.append(loss)
        print("[INFO] epoch #{}, loss={:.7f}".format(each_epoch + 1, loss))

        gradient = training_data_points.T.dot(error) / training_data_points.shape[0]
        if method == 0:
            weights = gradient_descent(weights, gradient, lr)
        else:
            weights = perceptron_algo(weights, gradient, lr)

    plot_results(method, weights, training_data, labels,loss_history, epochs)

def gradient_descent(weights, gradient, lr):
    """
    a(k + 1) = a(k) − η(k)∇J(a(k))
    ∇J(a) = y(k)
    :param weights:
    :param gradient:
    :param lr:
    :return:
    """"""
    # """

    weights += -(lr * gradient)
    return weights

def perceptron_algo(weights, gradient, lr):
    """
    a(k + 1) = a(k) + η(k)∇J(a(k))
    ∇J(a) = y(k)
    :param weights:
    :param gradient:
    :param lr:
    :return:
    """"""
    # """

    weights += (lr * gradient)
    return weights

def find_hypothesis(x):
    """
    using sigmoid function for the hypothesis
       J(a(k)) = dot(a'Y) > 0.5 -> clssified
                 dot(a'Y) < 0.5 -> Misclassified
    :return:
    """
    # compute and return the sigmoid activation value for a
    # given input value
    return 1.0 / (1 + np.exp(-x))

def augument_train_vector(data_points):
    """
    augument input/train vector x into y i.e. [x1, x2, x3 ...... xn, 1]

    :return:
    """
    return np.c_[np.ones((data_points.shape[0])), data_points]

def plot_results(method, W, X,labels, loss, epochs):
    Y = (-W[0] - (W[1] * X)) / W[2]
    if method == 0:
        graph = "Gradient descent: Classification between Class 1 and Class 2"
    else:
        graph = "Perceptron Algo: Classification between Class 1 and Class 2"

    # plot the original data along with our lin e of best fit
    plt.figure()
    plt.title(graph)
    plt.scatter(X[:, 0], X[:, 1], marker="o", c=labels)
    plt.plot(X, Y, "r-")

    # construct a figure that plots the loss over time
    fig = plt.figure()
    plt.plot(np.arange(0, epochs), loss)
    fig.suptitle("Training Loss")
    plt.xlabel("Epoch #")
    plt.ylabel("Loss")
    plt.show()

def classify():
    pass

def main():
    " method = 0 for gradient descent and 1 for perceptron"
    features = grab_data('sample_gd.csv')
    # split_data()
    optimize_weight_vector(method=0, features=features)
    # classify()

if __name__ == '__main__':
    main()


#!/usr/bin/env python

'''
Adapted from geopriv4j (Java) packaged: https://dl.acm.org/doi/pdf/10.1145/3403896.3403968 (Fan and Gunja, 2020);
/*
 * In this method we will be generating a new location (z) with a probability (p) that
 * reduces as the distance increase within a certain radius (r).
 *
 * As in Geopriv4j, this algorithm is based on the paper by Andrés, Miguel E., et al.
 * "Geo-indistinguishability: Differential privacy for location-based systems."
 * Proceedings of the 2013 ACM SIGSAC conference on Computer & communications
 * security. 2013. https://doi.org/10.1145/2508859.2516735
'''

import numpy as np
#Define constant earth radius
earth_radius = 6.3781e6

#Convert degrees to radians
def rad_of_deg(ang):
	return np.radians(ang)

#Convert radians to degrees
def deg_of_rad(ang):
	return np.degrees(ang)

#Define Lambda distribution
def LambertW(x):
	#Min diff decides when the while loop ends
	min_diff = 1e-10
	if (x == -1 / np.e):
		return -1
	elif ((x < 0) and (x > -1/np.e)):
		q = np.log(-x)
		p = 1
		while (abs(p-q) > min_diff):
			p = (q * q + x / np.exp(q)) / (q + 1)
			q = (p * p + x / np.exp(p)) / (p + 1)
		#determine the precision of the float number to be returned
		return (np.round(1000000 * q) / 1000000)
	elif (x == 0):
		return 0
	else:
		return 0

def inverseCumulativeGamma(epsilon,z):
	x = (z - 1) / np.e
	return -(LambertW(x) + 1)/epsilon

def alphaDeltaAccuracy(epsilon,delta):
	return inverseCumulativeGamma(epsilon,delta)

def expectedError(epsilon):
	return 2/epsilon

def addPolarNoise(epsilon,lng,lat):
	#Random number in [0, 2*pi)
	theta = np.random.rand()*np.pi*2
	#Random number in [0,1)
	z = np.random.rand()
	r = inverseCumulativeGamma(epsilon,z)
	return addVectorToPos(lng,lat,r,theta)

def addVectorToPos(lng,lat,distance,angle):
	ang_distance = distance / earth_radius
	lat1 = rad_of_deg(lat)
	lon1 = rad_of_deg(lng)
	lat2 = np.arcsin(np.sin(lat1)*np.cos(ang_distance) + np.cos(lat1)*np.sin(ang_distance)*np.cos(angle))
	lon2 = lon1 + np.arctan2(np.sin(angle)*np.sin(ang_distance)*np.cos(lat1),np.cos(ang_distance)-np.sin(lat1)*np.sin(lat2))
	lon2 = (lon2 + 3*np.pi) % (2*np.pi) - np.pi #Normalize to -180 to +180
	latnew = deg_of_rad(lat2)
	lngnew = deg_of_rad(lon2)
	return lngnew,latnew

#Geoindistinguishability algorithm
class LaplaceAlgorithm:
	#Init
	def __init__(self,epsilon=0.1):
		self.epsilon = epsilon

	def generate(self,lng,lat):
		#print(str(lng)+','+str(lat)+','+str(self.epsilon))
		return addPolarNoise(self.epsilon, lng, lat)


import numpy as np
import math

def normalize(vector):
    ''' Return the input vector divided by its norm. The resulting vector is of magnitude 1
    The input vector must a list. '''
    magn = magnitude(vector)
    v = np.asarray(vector, dtype=float).copy()
    v /= magn

    if isinstance(vector, np.ndarray):
        return v
    else:
        return v.tolist()

def magnitude(vector):
    sqr_sum = 0
    for val in vector:
        sqr_sum += val**2
    return math.sqrt(sqr_sum)

def cartesian2spherical(xyz):
    x = xyz[0]
    y = xyz[1]
    z = xyz[2]

    radial = magnitude(xyz)
    azimuth = math.atan2(y, x)
    elevation = math.atan2(z, magnitude((x, y)))
    return [radial, azimuth, elevation]

def spherical2cartesian(rae):
    radial = rae[0]
    azimuth = rae[1]
    elevation = rae[2]

    x = radial*math.cos(azimuth)*math.cos(elevation)
    y = radial*math.sin(azimuth)*math.sin(elevation)
    z = radial*math.sin(elevation)
    return [x, y, z]

def axisAngle_to_Rmat(axis, angle):
    """
    Return the rotation matrix associated with counterclockwise rotation about
    the given axis by theta radians.
    http://stackoverflow.com/a/6802723
    """
    axis = np.asarray(axis)
    axis = axis/math.sqrt(np.dot(axis, axis))
    a = math.cos(angle/2.0)
    b, c, d = -axis*math.sin(angle/2.0)
    aa, bb, cc, dd = a*a, b*b, c*c, d*d
    bc, ad, ac, ab, bd, cd = b*c, a*d, a*c, a*b, b*d, c*d
    return np.array([
        [aa+bb-cc-dd, 2*(bc+ad), 2*(bd-ac), 0.0],
        [2*(bc-ad), aa+cc-bb-dd, 2*(cd+ab), 0.0],
        [2*(bd+ac), 2*(cd-ab), aa+dd-bb-cc, 0.0],
        [0.0, 0.0, 0.0, 1.0]
        ])

def clean_taitbryan_angle(thetas):
    """
    tait-bryan angle's space is not uniform and we must be carful when theta_y tends to +-pi/2.
    Indeed, when theta_y is +-pi/2, there is an infinite number of combination (theta_x, theta_z) describing the same rotaton.
    In that case, we choose to set theta_z to 0, and compute theta_x accordingly.
    :param thetas: [theta_x, theta_y, theta_z]
    :return: corrected thetas: [theta_x, theta_y, theta_z]
    """
    thetas = thetas + [] # deep copy to avoid bad surprises
    R = thetas_to_Rmat(thetas)
    thetas = Rmat_to_thetas(R)

    pi = np.pi
    thetas[1] = min(pi, max(-pi, thetas[1]))  # clip theta_y in range [-pi/2, pi/2] because of rounding errors

    # most of the time we won't fall in the if case
    if pi / 2 - abs(thetas[1]) < 1e-6:
        thetas[0] -= math.copysign(thetas[2], thetas[1])
        thetas[2] = 0

    # force theta_x and theta_z to stay in range [-pi, pi)
    thetas[0] = (thetas[0] + pi) % (2 * pi) - pi
    thetas[2] = (thetas[2] + pi) % (2 * pi) - pi
    return thetas

def Rmat_to_axisAngle(R):
    #todo test this function
    """
    :param R:
    :return:
    """
    angle = math.acos(( R[0,0] + R[1,1] + R[2,2] - 1)/2)
    x = (R[2,1] - R[1,2]) / math.sqrt((R[2,1] - R[1,2])**2 + (R[0,2] - R[2,0])**2 + (R[1,0] - R[0,1])**2)
    y = (R[0,2] - R[2,0]) / math.sqrt((R[2,1] - R[1,2])**2 + (R[0,2] - R[2,0])**2 + (R[1,0] - R[0,1])**2)
    z = (R[1,0] - R[0,1]) / math.sqrt((R[2,1] - R[1,2])**2 + (R[0,2] - R[2,0])**2 + (R[1,0] - R[0,1])**2)
    axis = [x,y,z]
    return axis, angle

def thetas_to_Rmat(thetas, homogenous=True):
    """
    Compute the rotation matrix R, given the Tait-Bryan angle vector thetas = [theta_x, theta_y, theta_z]
    The rotation is applied in this order: R = Rz * Ry * Rx
    :param thetas: the 3 angles [theta_x, theta_y, theta_z] in radian
    :param homogenous: if homogenous=True R will be (4x4), if homogenous=False R will be (3x3)
    :return: Rotation matrix R
    """

    cx = math.cos(thetas[0])
    sx = math.sin(thetas[0])
    cy = math.cos(thetas[1])
    sy = math.sin(thetas[1])
    cz = math.cos(thetas[2])
    sz = math.sin(thetas[2])

    Rx = np.matrix([[1.0, 0.0, 0.0],
                    [0.0, cx, -sx],
                    [0.0, sx, cx]])

    Ry = np.matrix([[ cy, 0.0,  sy],
                    [0.0, 1.0, 0.0],
                    [-sy, 0.0,  cy]])

    Rz = np.matrix([[ cz, -sz, 0.0],
                    [ sz,  cz, 0.0],
                    [0.0, 0.0, 1.0]])
    R = Rz * Ry * Rx

    if homogenous:
        R = np.hstack((R, np.array([[0], [0], [0]])))
        R = np.vstack((R, np.array([0,0,0,1])))
    return R

def quaternion_to_Rmat(qx, qy, qz, qw):
    #TODO test this function
    """
    found at: http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.htm
    :param qx:
    :param qy:
    :param qz:
    :param qw:
    :return:
    """
    R = np.matrix([
        [1 - 2*qy**2 - 2*qz**2,	    2*qx*qy - 2*qz*qw,  	2*qx*qz + 2*qy*qw,  0],
        [    2*qx*qy + 2*qz*qw,	1 - 2*qx**2 - 2*qz**2, 	    2*qy*qz - 2*qx*qw,  0],
        [    2*qx*qz - 2*qy*qw,	    2*qy*qz + 2*qx*qw, 	1 - 2*qx**2 - 2*qy**2,  0],
        [                    0,                     0,                      0,  1]
    ])
    return R

def copysign0(a, b):
    """
    same as math.copysign but handle the 0 case
    """
    if b == 0:
        return 0
    else:
        return math.copysign(a, b)

def Rmat_to_quaternion(R):
    """
    code founded at: http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
    :param R:
    :return:
    """
    # the use of max is just a safeguard against rounding error
    qw = math.sqrt(max(0, 1 + R[0,0] + R[1,1] + R[2,2])) / 2
    qx = math.sqrt(max(0, 1 + R[0,0] - R[1,1] - R[2,2])) / 2
    qy = math.sqrt(max(0, 1 - R[0,0] + R[1,1] - R[2,2])) / 2
    qz = math.sqrt(max(0, 1 - R[0,0] - R[1,1] + R[2,2])) / 2

    # copysign takes the sign from the second term and sets the sign of the first without altering the magnitude
    qx = copysign0(qx, R[2,1] - R[1,2])
    qy = copysign0(qy, R[0,2] - R[2,0])
    qz = copysign0(qz, R[1,0] - R[0,1])
    return qx, qy, qz, qw

def Rmat_to_thetas(R):
    """
    Extract Tait-Bryan angles from rotation matrix
    :param R:
    :return: len-3 list of theta angles x, y, z in radian
    """
    singular_y = math.sqrt(R[0, 0] * R[0, 0] + R[1, 0] * R[1, 0])
    th_y = math.atan2(-R[2, 0], singular_y)  # similar to asin(-R[2, 0])
    singular = singular_y < 1e-6

    if not singular:
        # most of the time we fall in the non-singulare case
        th_x = math.atan2(R[2, 1], R[2, 2])
        th_z = math.atan2(R[1, 0], R[0, 0])
    else:
        # When th_y tends to pi/2 or -pi/2, then x and z rotation are mixed together leading to an infinity of solution.
        # So we set th_z to 0 to have a single solution
        th_x = math.atan2(-R[1, 2], R[1, 1])
        th_z = 0

    thetas = [th_x, th_y, th_z]
    return thetas

def thetas_to_quaternion(thetas):
    # TODO: enhance the doc
    """
    go through Rmat
    :param thetas:
    :return:
    """
    R = thetas_to_Rmat(thetas)
    qx, qy, qz, qw = Rmat_to_quaternion(R)
    return qx, qy, qz, qw

def quaternion_to_thetas(qx, qy, qz, qw):
    # TODO: write the doc for this taitbryan function
    R = quaternion_to_Rmat(qx, qy, qz, qw)
    thetas = Rmat_to_thetas(R)
    return thetas

def translation_mat4(translation_vector):
    T = np.identity(4, dtype='float')
    T[0, 3] = translation_vector[0]
    T[1, 3] = translation_vector[1]
    T[2, 3] = translation_vector[2]
    return T

def mat4_dot_vec3(Mat4, vec3):
    """
    INPUTS
        Mat4: A 4x4 transformation matrix
        vec3: A 3x1 vector containing coordinates [x,y,z]
    OUTPUT
        A 3x1 vector
    """

    # Turn the vec3 into a vec4 by appending a 1 to it (resulting in a 4x1 vector)
    vec4 = np.asarray(list(vec3)+[1.0]).copy()
    # compute the dot product between Mat4 and vec4 resulting in a (4x1) vector
    res4 = np.dot(Mat4, vec4)
    # return the first 3 entries of the vector
    return res4[:3].tolist()

def plot(fig, ax, tag_xyz, locator_xyz, locator_pyr, tag_local_xyz, tag_local_rae):

    xs = [tag_xyz[0]]
    ys = [tag_xyz[1]]
    zs = [tag_xyz[2]]
    ax.scatter(xs, ys, zs, c='r', marker='o')


import numpy as np

def sigmoid(val):
    if val < 0:
        return 1 - 1/(1 + np.exp(val))
    else:
        return 1/(1 + np.exp(-val))

def v_func(val):
    return abs(val/(np.sqrt(1 + val*val)))

def u_func(val, alpha=2, beta=1.5):
    return abs(alpha * np.power(abs(val), beta))

def get_trans_function(shape):
    if (shape.lower() == 's'):
        return sigmoid

    elif (shape.lower() == 'v'):
        return v_func

    elif(shape.lower() == 'u'):
        return u_func

    else:
        print('\n[Error!] We don\'t currently support {}-shaped transfer functions...\n'.format(shape))
        exit(1)


# -*- coding: utf-8 -*-
"""
Created on Wed Jan  9 14:11:39 2019

@author: Tian
"""

import numpy as np

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

def sigmoidPrime(x):
    v = sigmoid(x)
    return v * (1 - v)

def sigmoidDerivative(x, y):
    return y * (1 - y)

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

def tanhPrime(x):
    t=tanh(x);
    return 1-t^2;

def tanhDerivative(x, y):
    return 1-y^2;

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

def reluPrime(x):
    return np.where(x>0, 1, 0)

def reluDerivative(x, y):
    return np.where(y, 1, 0)

def softplus(x):
	return np.log1p(np.exp(x))

def softplusPrime(x):
	return sigmoid(x)

def softplusDerivative(x, y):
	return sigmoid(y)


import numpy as np
from scipy.ndimage import convolve

# Few stencils for first and second order derivatives.

def d1(u, axis, dx=1.0e0, n_points=5, mode="wrap"):
    """
    First order derivative of a two dimensional array.

    axis     : 'x' or 'y'
    n_points : Number of points to use in the stencil.
    mode     : scipy convolve mode. Use 'wrap' for periodic.
    """

    dx_inv = dx
    if n_points == 5:
        stencil = (dx_inv / 12.0e0) * np.array([[-1, 8, 0, -8, 1]])
    elif n_points == 3:
        stencil = (dx_inv / 2.0e0) * np.array([[-1, 0, 1]])
    if axis == "x":
        return convolve(u, stencil, mode=mode)
    if axis == "y":
        return convolve(u, stencil.T, mode=mode)

def d2(u, axis, dx=1.0e0, n_points=5, mode="wrap"):
    """
    Second order derivative of a two dimensional array.

    axis     : 'x' or 'y'
    n_points : Number of points to use in the stencil.
    mode     : scipy convolve mode. Use 'wrap' for periodic.
    """
    dx_inv = dx
    if n_points == 5:
        stencil = (dx_inv ** 2 / 12.0e0) * np.array([[-1, 16, -30, 16, -1]])
    elif n_points == 3:
        stencil = (dx_inv ** 2) * np.array([[1, -2, 1]])
    if axis == "x":
        return convolve(u, stencil, mode=mode)
    if axis == "y":
        return convolve(u, stencil.T, mode=mode)


import numpy as np
import time
from scipy.integrate import odeint
from math import *
import matplotlib.pyplot as plt

def MassSpringDamper(state,t,F, mode='critically_damped'):
	x = state[0]
	xd = state[1]
	if mode=='oscillations':
		m = 1.0; k = 2; b = 0 # undapmped: oscillations
	elif mode=='underdamped':
		m = 1.0; k = 2; b = 2*sqrt(m*k)-2 # underdamped
	elif mode=='overdamped':
		m = 1.0; k = 2; b = 2*sqrt(m*k)+2 # overdamped
	else:
		m = 1.0; k = 2; b = 2*sqrt(m*k) # critically damped
	# print mode+", damping ratio="+str(b/2/sqrt(m*k))
	xdd = -(b/m)*xd - (k/m)*x + F/m
	return [xd, xdd]

y0 = [np.pi - 0.1, 0.0]
M = 10
t = np.linspace(0, 4*pi, 101)
modes = ['underdamped', 'overdamped', 'critically_damped', 'oscillations']

plt.rcParams.update({'font.size': 30})
plt.figure(figsize=(20,20))
plt.grid()
plt.title('Impedance modeles')
plt.xlabel('t')
plt.ylabel('x(t)')
for mode in modes:
	# differential equation solution
	sol = odeint(MassSpringDamper, y0, t, args=(M,mode,))
	plt.plot(t, sol[:, 0], label=mode, linewidth=4)
	# plt.plot(t, sol[:, 1], 'g', label='omega(t)')
plt.legend(loc='best')

# close windows if Enter-button is pressed
plt.draw()
plt.pause(1)
raw_input('Hit Enter to close')
plt.close('all')


import numpy as np
from scipy.stats import norm
from math import log, sqrt, exp, pi

class Opcion:
    def __init__(self,s,k,r,sigma,T, kind):
        """ s = spot, k = strike , r = tasa (decimal), sigma = volatilidad (decimal), T = dias """
        self.s = float(s)
        self.k = float(k)
        self.r = float(r)
        self.sigma = float(sigma)
        self.T = float(T/360)
        self.kind = kind
        self.d1 =( log(self.s/self.k) + ( self.r + (self.sigma**2 /2) )*self.T ) /   (self.sigma * sqrt(self.T) )
        self.d2 = self.d1 - (self.sigma * sqrt(self.T))
        self.discount = exp(-(self.r) * self.T)

    def value(self):
        if (self.kind == "call"):
            call = self.s*norm.cdf(self.d1) - self.k*self.discount*norm.cdf(self.d2)
            return(call)
        else:
            put = self.k*self.discount*norm.cdf(-self.d2) - self.s*norm.cdf(-self.d1)
            return(put)

    def delta(self):
        if (self.kind == "call"):
            return(norm.cdf(self.d1))
        else:
            return(norm.cdf(self.d1)-1)

    def gamma(self):
        gamma = exp((-self.d1 ** 2)/2)  / (self.s * self.sigma * sqrt(self.T) * 2 * pi)
        return(gamma)

    def vega(self):
        vega = self.s * sqrt(self.T) * exp((-self.d1 ** 2)/2) / sqrt(2 * pi)
        return(vega)

    def valuation_curve(self):
    #Establish uper and lower limit for range
        if self.s > self.k:
            liminf = 0.9 * self.s
            limsup = 1.1 * self.k
        else:
            liminf = 0.9 * self.k
            limsup = 1.1 * self.s
        rango = np.arange(start = liminf, stop = limsup, step = self.s/100).tolist()
        valores = []

        for i in rango:
            x = Opcion(s = i,k = self.k ,r = self.r ,sigma = self.sigma, T = self.T, kind = self.kind)
            valores.append(x.value())
        val_cur = [rango, valores]
        return val_cur

a = Opcion(s = 50.6 ,k = 50 ,r = 0.05 ,sigma = 0.39 ,T = 3, kind = "put")
print(a.value())
print(a.delta())
print(a.gamma())
print(a.vega())
res = a.valuation_curve()
import matplotlib.pyplot as plt
plt.plot(res[0], res[1])
plt.show()


import numpy as np

def f(y):
    return -50*y

def split(inte, h):
    """ Split the interval

    Args:
        inte: list/ndarray, the interval to be splited
        h: double, the step length of the split

    Returns:
        x: list/ndarray, the split of the interval
    """
    # the endpoints of the interval
    a, b = inte
    n = int((b - a)/h)
    x = np.linspace(a, b, n+1)

    return x

def implict_Euler(x, func, ini):
    """ Solve the ordinary equation y' = -50y, y(0)=100

    Args:
        x: list/ndarray, the split of the interval
        func: the function f(y)
        ini: double, the initial value for IVP

    Returns:
        y: list/ndarray, the values of the function y(x) at every x
    """
    n = len(x)
    h = x[1] - x[0]

    # iteration
    y = np.zeros(n)
    y[0] = ini
    for i in range(1, n):
        y[i] = y[i-1]/(1 + 50*h)

    return y

if __name__ == '__main__':
    h = [0.1, 0.05, 0.01, 0.001]
    for h0 in h:
        print(f"The numerical solution of ODE for h = {h0} is as follows:")
        inte = [0, 20*h0]
        x = split(inte, h0)
        y = implict_Euler(x, f, 100)
        for item in zip(x, y):
            print(f"{item[0]:.3f}\t{item[1]:.16f}")


import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import os

cmap = 'copper'

def plot_faces(ims,imshape,cmap='gray'):
    fig = plt.figure(figsize=(18,3),constrained_layout=True, dpi=100)
    gs  = fig.add_gridspec(1,len(ims))
    for i,im in enumerate(ims):
        ax = fig.add_subplot(gs[0,i],xticks=[],yticks=[])
        ax.imshow(im.reshape(imshape),cmap=cmap)

def plot_pca_projection(
        im_face,
        mean_face,
        eigenfaces,
        coeffs,
        n_components=None,
        k_components=None,
        imshape=(0,0),
        cmap='gray'
    ):

    n_components = len(eigenfaces) if n_components is None else n_components
    k_components = n_components if k_components is None else k_components

    def plot_component(fig,pos,im,text='',left_align=True,plus=0,l2_norm=None):
        ax = fig.add_subplot(gs[pos[0],pos[1]],xticks=[],yticks=[])
        ax.imshow(im.reshape(imshape),cmap=cmap)
        ax.set_title(text,pad=-0.1)
        if left_align:
            ax.text(-40,103,'+',fontsize=12,alpha=plus)
        if l2_norm is not None:
            ax.text(45,220,f'{l2_norm:.1f}',fontsize=12)

    fig = plt.figure(figsize=(18,3),constrained_layout=True, dpi=100)
    gs  = fig.add_gridspec(2,n_components+5)
    fig.set_constrained_layout_pads(w_pad=0, h_pad=0, hspace=0, wspace=0)

    # Mean axes
    plot_component(fig,[0,2],im=mean_face,text=f'$mean\,face$',left_align=False)
    plot_component(fig,[1,2],im=mean_face,text=r'$coeffs\longrightarrow $',left_align=False)

    # Other axes
    proj_face = mean_face
    for k in range(n_components):
        eig_face = eigenfaces[k]
        plot_component(fig,[0,k+3],im=eig_face,text=f'$eigen\,face_{k+1}$')

        if k in range(k_components):
            coeff = coeffs[k]
            proj_face += eig_face * coeff
            l2_norm = np.sum(np.square(im_face - proj_face)) / len(im_face)
            plot_component(fig,[1,k+3],im=proj_face,text=f'{coeffs[k]:4.1f}',plus=1,l2_norm=l2_norm)

    # Left & Right axes
    axL = fig.add_subplot(gs[:, :2],xticks=[],yticks=[]); axL.set_title('Original')
    axR = fig.add_subplot(gs[:,-2:],xticks=[],yticks=[]); axR.set_title('Projection')

    axL.imshow(im_face.reshape(imshape),cmap=cmap)
    axR.imshow(proj_face.reshape(imshape),cmap=cmap)

    axL.text(45,203,f'$L2 \, norms \longrightarrow$',fontsize=12)
    axR.text(75,203,f'{l2_norm:.1f}',fontsize=12)
    plt.show()

# %% [markdown]
# ### Training Data
# In this study, we have 10 face samples of different identities from **Yale Face Database B**. These image samples are stored in gray-scale pixel intensities with $(192,168)$ dimensions. Below routine reads and flattens them to construct a training set **X** with a shape of $(10,32256)$. Original faces are displayed in **copper color-map**.

# %%
img_vector_list = []
path_main = "./face_Yale/"
img_path_list = [os.path.join(path_main,path_img) for path_img in os.listdir(path_main)]
for img_path in img_path_list:
    img = mpimg.imread(img_path)
    imshape = img.shape # (192,168)
    img_vector = img.flatten() # (32256,)
    img_vector_list.append(img_vector)
X = np.array(img_vector_list) # (10,32256)
plot_faces(X,imshape=imshape,cmap=cmap)

# %% [markdown]
# ### PCA & Eigenfaces
# PCA implementation of Sklearn library have three basic parameters: **n_components**, **whiten** and **svd_solver**. <br>
#
# If **n_components** is in $[0,1]$ interval; then, algorithm returns you the components which explains the dataset variance by that specified value. If it is not specified ($None$), we can reach all components to make further analysis. However, it is good to know that evaluated maximum number of components are limited to the smallest size of the data:
# $$n\_components = min(n\_samples,n\_features) = 10 $$
#
# **n_whiten** is such a normalization that principal components are multiplied by the square root of $n\_samples$ and divided by singular values. This process makes uncorrelated outputs to have unit variances. Since, whitening changes (eliminates some information) the training data, keep it $False$ for our toy example. (False by default)
#
# **svd_solver** determines how the eigen-vectors are calculated. In our case, we have large feature set ($n\_features = 32256$) and this makes the calculation of eigen-value and eigen-vectors not efficient. Choosing $svd\_solver = auto$ will result in use of Randomized Singular Value Decomposition method (Halko et al) to solve this problem. It is an intelligent way to aproximate the first principal components quickly.
#

# %%
from sklearn.decomposition import PCA

pca = PCA(n_components=None,whiten=False,svd_solver='auto').fit(X)
eigenfaces = pca.components_
coeffs = pca.transform(X)

# %% [markdown]
# Evaluated PCA components are stored in **eigenfaces** variable. **coeffs** is the weights for each eigenface to transform the original face to projection space.
# $$coeffs = X \cdot eigenfaces^T $$
# Here are the first 10 eigenfaces of the training set, respectively:
#

# %%
plot_faces(pca.components_,imshape=imshape,cmap=cmap)

# %% [markdown]
# Because $9$ eigenfaces can cover all the training samples $10^{th}$ eigen face seems like it does not exist.

# %% [markdown]
# ### How to choose $k$ eigenfaces?
# Explained variance is a measurement of importance for each component to represent the data. One component's explained variance is approximately equal to the ratio of corresponding eigen-value to the sum of all eigen-values. Therefore, we can arrive an expression of cumulative sum by combining $k$ components:
# $$ Explained \, Var(k) = \frac{\sum_i^k \lambda_i}{ \sum_{i}^N \lambda_i } $$
# where N is the total number of components and $k\leq N$.
#
# In the below figure, horizontal axis is the number of principal components (eigenfaces) and vertical axis is the explained variance approximation. Inspecting the cumulative explained variance (orange plot) can lead us to choose $k=5$ for $80$% explained variance desire.
#
# Again, $10^{th}$ eigenface has no impact to the data representation and the cumulative explained variance succeed to reach the top $1.0$ with $9$ eigenfaces.

# %%
fig = plt.figure(figsize=(10,5))
plt.plot(np.arange(1,11,1),pca.explained_variance_ratio_,marker='x',\
    label='explained variance by $i^{th}$ eigenface')
plt.plot(np.arange(1,11,1),np.cumsum(pca.explained_variance_ratio_),marker='o',\
    label='cumulative explained variance')
plt.xticks(np.arange(0,11,1)); plt.yticks(np.arange(0,1.1,0.1))
plt.xlabel('PCA components (eigenfaces)'); plt.ylabel('explained variance')
plt.grid(); plt.legend(); plt.show()

# %% [markdown]
# ### Projection and Error
# Eigenfaces and their identity-based coefficients are evaluated, already. Projection is simply adding the weighted summation of these eigenfaces and coefficients to the mean face of the training samples. This progress is visualized based on an illustration made by [Jake VanderPlas](https://jakevdp.github.io/PythonDataScienceHandbook/06.00-figure-code.html#Digits-Pixel-Components). My implementation is available at the bottom of this report.
#
# The original face is located in the most left of the below figure. The eigenfaces are followed the mean face in the first row of the 2D face array. Between the first and second row of the 2D face array, coefficients correspond to related eigenfaces are placed. In the second row, there are projections with $i=\{1,...,k\}$ eigenfaces, where $i$ increased by column and $k$ is the specified eigenface number in the last section. The values under these projections are their L2 norms to the original face. $k^{th}$ column projection starting after the mean face is the final projection and also displayed in the most right of the figure.

# %%
k = 5
for i in range(1,k+1):
    plot_pca_projection(
        im_face=X[8],
        mean_face=np.mean(X,axis=0),
        eigenfaces=eigenfaces,
        coeffs=coeffs[8],
        n_components=9,
        k_components=i,
        imshape=imshape,
        cmap=cmap
        )

# %% [markdown]
# ### Utilizing PCA for Storage Purpose
# Let's make an approximate analysis on a question of how much memory space we are required to store the **10** faces.
# Say, **S** is the memory allocation for one face of the dataset **X**. Thus, in total **10S** space is required without PCA.
# In this application, we have found that $k=5$ eigenfaces performed acceptable results to represent the **X**. Therefore, we might store **5** eigenfaces, **1** mean face and relatively small coefficients matrix to reconstruct the **X**. If memory need for coefficients is denoted as **eps** total memory need can be evaluated to **6S+eps**.
#
# Finally, we infer from the assumptions above that PCA has provided **almost 40%** gain in terms of memory space.

# %% [markdown]
# ### Projections for Each Identity

# %%
for id in range(len(X)):
    plot_pca_projection(
        im_face=X[id],
        mean_face=np.mean(X,axis=0),
        eigenfaces=eigenfaces,
        coeffs=coeffs[id],
        n_components=9,
        k_components=None,
        imshape=imshape,
        cmap=cmap
    )


"""
Metaheuristic Optimization Using Population-Based Simulated Annealing.

Copyright (c) 2021 Gabriele Gilardi

Features
--------
- The code has been written and tested in Python 3.8.8.
- Variables can be real, integer, or mixed real/integer.
- Variables can be constrained to a specific interval or value setting the
  lower and the upper boundaries.
- Neighboroud search is performed using a normal distribution along randomly
  chosen dimensions.
- A nonlinear decreasing schedule is used for the temperature and for the
  standard deviation in the neighboroud search.
- Search space can be normalized to improve convergency.
- To improve the execution speed the algorithm has been designed removing
  almost all loops on the agents.
- An arbitrary number of parameters can be passed (in a tuple) to the function
  to minimize.
- Usage: python test.py <example>.

Main Parameters
---------------
example
    Name of the example to run (Parabola, Alpine, Tripod, and Ackley.)
func
    Function to minimize. The position of all agents is passed to the function
    at the same time.
LB, UB
    Lower and upper boundaries of the search space.
nPop >=1, epochs >= 1
    Number of agents (population) and number of iterations.
T0 > 0
    Initial temperature.
0 < alphaT < 1
    Temperature reduction rate.
0 < alphaS <= 1
    Standard deviation (neighboroud search) reduction rate.
nMove > 0
    Number of neighbours of a state evaluated at each epoch.
0 < prob < 1
    Probability the dimension of a state is changed.
sigma0 > 0
    Initial standard deviation used to search the neighboroud of a state.
IntVar
    List of indexes specifying which variable should be treated as integer.
    If all variables are real set <IntVar> = <None>. Indexes are specified
    in the range 0 to <nVar-1>. It cannot be used when the search space is
    normalized.
normalize = True, False
    Specifies if the search space should be normalized. If <True>, parameter
    <sigma0> is applied to the normalized search space.
args
    Tuple containing any parameter that needs to be passed to the function. If
    no parameters are passed set <args> = <None>.
nVar
    Number of variables.
nIntVar >= 0
    Number of integer variables.
X0
    Global minimum point (used only to compare with the numerical solution).
seed
    Seeding value for the random number generator.

Examples
--------
There are four examples: Parabola, Alpine, Tripod, and Ackley.

- Parabola, Alpine, and Ackley can have an arbitrary number of dimensions,
  while Tripod has only two dimensions.

- Parabola, Tripod, and Ackley are examples where parameters (respectively,
  array X0, scalars kx and ky, and array X0) are passed using args.

- The global minimum for Parabola and Ackley is at X0; the global minimum for
  Alpine is at zero; the global minimum for Tripod is at [0,-ky] with local
  minimum at [-kx,+ky] and [+kx,+ky].

References
----------
- Simulated annealing @ https://en.wikipedia.org/wiki/Simulated_annealing
- Kirkpatrick et al., 1983, "Optimization by Simulated Annealing", JSTOR
  @ https://www.jstor.org/stable/1690046
- Jamil and Yang, 2013, "A Literature Survey of Benchmark Functions For Global
  Optimization Problems", arXiv @ https://arxiv.org/abs/1308.4008
"""

import sys
import numpy as np
from sa import SA

# Read example to run
if len(sys.argv) != 2:
    print("Usage: python test.py <example>")
    sys.exit(1)
example = sys.argv[1]

# Default parameters
nPop = 20
epochs = 1000
nMove = 100
T0 = 0.1
alphaT = 0.99
sigma0 = 0.1
alphaS = 0.98
prob = 0.5
normalize = False
IntVar = None
args = None
seed = 123

# Parabola: F(X) = sum((X - X0)^2)
# Xmin = X0
if (example == 'Parabola'):

    def Parabola(X, args):
        X0 = args
        F = ((X - X0) ** 2).sum(axis=1)
        return F

    func = Parabola
    nVar = 20
    UB = np.ones(nVar) * 500.0
    LB = - UB
    X0 = 1.1 * np.arange(0, nVar)
    args = (X0)

# Ackley: F(X)= + 20 + exp(1) - exp(sum(cos(2*pi*(X-X0))/n)
#               - 20*exp(-0.2*sqrt(sum((X-X0)^2)/n))
# Xmin = X0
elif (example == 'Ackley'):

    def Ackley(X, args):
        X0 = args
        n = float(X.shape[1])
        F = + 20.0 + np.exp(1.0) \
            - np.exp((np.cos(2.0 * np.pi * (X - X0))).sum(axis=1) / n) \
            - 20.0 * np.exp(-0.2 * np.sqrt(((X - X0) ** 2).sum(axis=1) / n))
        return F

    func = Ackley
    nVar = 10
    nPop = 50
    UB = np.ones(nVar) * 50.0
    LB = - UB
    X0 = np.ones(nVar) * 1.6789
    args = (X0)

# Tripod:
# F(x,y)= p(y)*(1 + p(x)) + abs(x + kx*p(y)*(1 - 2*p(x)))
#         + abs(y + ky*(1 - 2*p(y)))
# p(x) = 1 if x >= 0, p(x) = 0 if x < 0; p(y) = 1 if y >= 0, p(y) = 0 if y < 0
# Global minimum at [0,-ky], local minimum at [-kx,ky] and [kx,ky]; kx, ky > 0
elif (example == 'Tripod'):

    def Tripod(X, args):
        x = X[:, 0]
        y = X[:, 1]
        kx = args[0]
        ky = args[1]
        px = (x >= 0.0)
        py = (y >= 0.0)
        F = py * (1.0 + px) + np.abs(x + kx * py * (1.0 - 2.0 * px)) \
            + np.abs(y + ky * (1.0 - 2.0 * py))
        return F

    func = Tripod
    nVar = 2                # The equation works only with two dimensions
    kx = 20.0               # Args
    ky = 40.0
    UB = np.ones(nVar) * 100.0
    LB = - UB
    X0 = np.array([0.0, -ky])
    args = (kx, ky)

# Alpine: F(X) = sum(abs(X*sin(X) + 0.1*X))
# Xmin = 0
# Note: the solution is VERY sensitive to the parameter values and the
#       random generated numbers
elif (example == 'Alpine'):

    def Alpine(X, args):
        F = np.abs(X * np.sin(X) + 0.1 * X).sum(axis=1)
        return F

    func = Alpine
    nVar = 10
    sigma0 = 0.2
    alphaS = 1.0
    UB = np.ones(nVar) * 10.0
    LB = - UB
    X0 = np.zeros(nVar)

else:
    print("Function not found")
    sys.exit(1)

# Solve
np.random.seed(seed)
X, info = SA(func, LB, UB, nPop=nPop, epochs=epochs, nMove=nMove, T0=T0,
             alphaT=alphaT, sigma0=sigma0, alphaS=alphaS, prob=prob,
             IntVar=IntVar, normalize=normalize, args=args)
F, T_final, sigma_final = info

# Results
print("Function: ", example)
print("\nBest position:")
print(X)
print("\nCost: ", F[-1])
print("Final T: ", T_final)
print("Final sigma (avr): ", sigma_final.mean())
print("Error: {0:e}".format(np.linalg.norm(X0 - X)))


import numpy as np
import pandas as pd

def split_and_fit(data: np.ndarray, n_split:int,order_fit:int) -> np.ndarray :
    """Given the input numpy 1D array, split it into $(n_split)$ equal halves
    and perform a polynomial fit of order $(order_fit)$ on each half.
    Returns: A new 1D array of size n_split*(order_fit+1)
    containing the coefficients of the fit for each split"""

    splitted_data = np.array(np.split(data,n_split))
    x_axis = np.arange(len(splitted_data[0])) - len(splitted_data[0])//2
    output_array = np.zeros(n_split*(order_fit+1))

    for counter, row in enumerate(splitted_data):
        output_array[counter*(order_fit +1):
        counter*(order_fit +1)+order_fit+1 ] = np.polyfit(x=x_axis,
                                                          y=row,deg=order_fit)

    return output_array

def precompute_output(dataset:np.ndarray, n_splits:list, order_fits:list) -> np.ndarray:
    """Precomputes the output dataset for the polynomial feature extraction"""

    nelements_ = [i*(j+1) for i,j in zip(n_splits, order_fits)]
    nelements_ = [0] + nelements_
    nelems_cumsum = np.cumsum(nelements_)
    output_data = np.zeros((dataset.shape[0], np.sum(nelements_)))
    return output_data, nelems_cumsum

def poly_featextract_todf(output_data:np.ndarray, n_splits:list, order_fits:list ) -> pd.DataFrame:
    """Given the output of poly_featextract, here we return a dataframe with labelled coulmns"""
    cols = []
    polyfit_features = [f"{i}_{j}" for i in n_splits for j in range(1,i+1)]
    for i,j in zip(n_splits, order_fits):
        for k in polyfit_features:
            if int(k.split("_")[0]) == i:
                cols.extend([f"{k}_{l}" for l in np.arange(j,-1,-1)])
    return pd.DataFrame(output_data,columns=cols)

def poly_featextract(dataset:np.ndarray, n_splits:list, order_fits:list, as_df=False) -> np.ndarray:
    """Computes the polynomial-fit features for each example in the dataset
    Input:
        dataset: 2D array of shape mxn where m is the number of examples and
                n is the no of features
        n_splits: The number of equal splits features of the dataset
        order_fits: Order of the polynomial to be fit for each split in n_splits
    Returns:
        2D array with all the polyfit features for the dataset
    """
    if len(order_fits) != len(n_splits):
        raise ValueError("n_splits and order_fits are not equal. Please provide the order of polynomial for each split")

    # precompute the output_dataset for faster execution
    output_data, split_cumsum = precompute_output(dataset,n_splits, order_fits)

    for id_row, row in enumerate(dataset):
        for id_split, split_fit in enumerate(zip(n_splits, order_fits)):
            output_data[id_row,
                        split_cumsum[id_split]:
                        split_cumsum[id_split+1]] = split_and_fit(data=row,
                                                  n_split= split_fit[0],
                                                  order_fit= split_fit[1])
    if as_df == True:
        return poly_featextract_todf(output_data,n_splits, order_fits)
    else:
        return output_data


import numpy as np
import matplotlib.pyplot as plt

# pyplot - ключевой модуль библиотеки matplotlib

# Рисуем ф-ю y = x**2 * e**(-)x**2

# Создаем равномерно распределенное множество
X = np.linspace(0, 3, 1001, dtype=np.float32)

print(X)  # [0.    0.003 0.006 ... 2.994 2.997 3.   ]

# Возведем 'x' в квадрат

print(X**2)
# [0.000000e+00 9.000000e-06 3.600000e-05 ... 8.964036e+00 8.982009e+00
#  9.000000e+00]
# Операция происходит над каждым элементом массива в отдельности
# Все операции над массивами в NumPy совершаются с каждым членом отдельно.
# Если нужно иное, то на это есть соответствующие ф-и

# Дальше вычисляем ф-ю
Y = X**2 * np.exp( -(X**2) )
# Формулу применяем прямо над массивом целиком.
# Соответствующие циклы будут запрятаны внутрь бибилотеку numpy

# Посмотрим чему равен Y

print(Y)
# [0.0000000e+00 8.9999194e-06 3.5998706e-05 ... 1.1467591e-03 1.1285910e-03
#  1.1106882e-03]

# Видим, что сначала У растет, потом падает.
# Выведем еще одну ф-ю Z = sin(x) / e**x

Z = np.sin(X) / np.exp(X)

# Выводим Z
print(Z)  # [0.0000000e+00 8.9999194e-06 3.5998706e-05 ... 1.1467591e-03 1.1285910e-03 1.1106882e-03]

# Дальше попробуем ее нарисовать
# Простейший способ вывода графика
plt.plot( X, Y )  # первый - массив Х, второй - массив Y. Они должны быть одинаковыми.
# При необходимости, здесь можно указать, каким цветом нужно рисовать их линии.
# 'b-'  - 'b' - синяя, '-' - сплошная
# ф-я plot возвращает нам объект типа line2d

print(plt.plot(X, Z, 'r-'))  # используем красную сплошную линию
# [<matplotlib.lines.Line2D object at 0x0970F4C0>]  - тип

# Выводим на экран график
print(plt.show())  # получаем два графика на одних осях

# На вид график странный.
# Давайте посмотрим просто график sin(X)
S = np.sin(X)
plt.plot(X,S, 'g-')
plt.show()
# Здесь мы до числа pi не доехали. Синус меняет знак, когда х = pi. Тогда график норм.
# Вот так можно рисовать графики в простейшем случае.
# 1:22:58


from __future__ import absolute_import, division, print_function, unicode_literals
import math
import os
import numpy
import argparse

def parseArguments():
    desc = "Program that analyses a column of data, printing different statistical values and a histogram if desired."
    parser = argparse.ArgumentParser(description=desc)
    parser.add_argument("col", type=int, default=1, help="Column with the values (Integer values in [1, N]).")
    parser.add_argument("files", nargs='+', help="Files with the data to analyse.")
    parser.add_argument("-o", metavar="OUTLIERRANGE", type=float, default=1, help="Factor that widens the range for a value not to be considered as an outlier.")
    parser.add_argument("-p", nargs='?', type=int, const=10, metavar='bins', help="Plots histogram. Can take as argument the number of bins.")
    parser.add_argument("-e", action="store_true", default=False, help="If plotting, draw the error bars.")
    args = parser.parse_args()

    return args.col, args.files, args.p, args.o, args.e

def analyseData(data):
    numberOfElements = len(data)
    average = sum(data)
    average /= numberOfElements

    data.sort()
    minVal = data[0]
    maxVal = data[-1]
    median = data[numberOfElements//2]

    variance = 0
    for number in data:
        variance += math.pow(number - average, 2)

    variance = variance/(numberOfElements-1)

    standardDeviation = math.sqrt(variance)

    print("------------------------------")
    print("RESULTS")
    print("------------------------------")
    print("# of elements: ", numberOfElements)
    print("Average value: ", average)
    print("Std deviation: ", standardDeviation)
    print("Maximum vaule: ", maxVal)
    print("Minumum value: ", minVal)
    print("Median  value: ", median)
    print("------------------------------")

def returnListWithoutOutliers(data, outlierRange):
    """
        An outlier is defiend as a datapoint not in [Q1 - 1.5*IQR*outlierRange, Q3 + 1.5*IQR*outlierRange],
        where IQR is the interquartile range: Q3 - Q1
    """
    data.sort()

    dataPointsBefore = len(data)
    Q1 = data[dataPointsBefore//4]
    Q3 = data[3*dataPointsBefore//4]
    IQR = Q3 - Q1

    lowerFence = Q1 - 1.5 * IQR * outlierRange
    upperFence = Q3 + 1.5 * IQR * outlierRange

    filteredData = [i for i in data if i >= lowerFence and i <= upperFence]

    dataPointsAfter = len(filteredData)
    print('Removed ' + str(dataPointsBefore - dataPointsAfter) + ' outliers')

    return filteredData

def printFilenamesAbsolutePath(files):
    print("files:")
    for f in files:
        print(os.path.abspath(f))

def readDataFromFiles(files, column):
    data = []
    for f in files:
        tmpdata = numpy.loadtxt(f, unpack=True, usecols=[column])
        try:
            data.extend(tmpdata)
        except:
            data.append(tmpdata)
    return data

def main():
    column, files, bins, outlierRange, plotErrorBars = parseArguments()
    # We count columns starting with 1
    column -= 1

    data = readDataFromFiles(files, column)

    printFilenamesAbsolutePath(files)
    print("---------------------------")
    print("Before removing outliers:")

    analyseData(data)

    filteredData = returnListWithoutOutliers(data, outlierRange)

    print("---------------------------")
    print("After removing outliers:")

    analyseData(filteredData)

    if bins:
        import histogram
        plotErrorBars = plotErrorBars not in ['False', 'false', 0]
        histogram.plot_histogram(data, bins, plotErrorBars, 1, "Before removing outliers")
        histogram.plot_histogram(filteredData, bins, plotErrorBars, 2, "After removing outliers (%.2f * IQR)" % (outlierRange*1.5))
        # To keep plots "alive"
        raw_input()

if __name__ == '__main__':
    main()


import scipy as sp
import math

logBase = 2

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

def coverEntropy(cover: list, allNodes: list) -> float:
    allEntr = []
    for com in cover:
        fractionIn = len(com) / len(allNodes)
        allEntr.append(sp.stats.entropy([fractionIn, 1 - fractionIn], base=logBase))

    return sum(allEntr)

# https://github.com/RapidsAtHKUST/CommunityDetectionCodes
def comPairConditionalEntropy(cl: set, clKnown: set, allNodes: set) -> float:

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

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

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

    return conditionalEntropy  # *nbNodes

def coverConditionalEntropy(
    cover: list, coverRef: list, allNodes: set, normalized: bool = False
) -> float:
    # cover and coverRef and list of set

    allMatches = []

    for com in cover:
        matches = [
            (com2, comPairConditionalEntropy(com, com2, allNodes)) for com2 in coverRef
        ]
        bestMatch = min(matches, key=lambda c: c[1])
        HXY_part = bestMatch[1]
        if normalized:
            HX = partialEntropyAProba(len(com) / len(allNodes)) + partialEntropyAProba(
                (len(allNodes) - len(com)) / len(allNodes)
            )
            if HX == 0:
                HXY_part = 1
            else:
                HXY_part = HXY_part / HX
        allMatches.append(HXY_part)

    to_return = sum(allMatches)
    if normalized:
        to_return = to_return / len(cover)
    return to_return

def onmi(
    cover: list, coverRef: list, allNodes: set = None, variant: str = "LFK"
) -> float:
    """
    Compute Overlapping NMI

    This implementation allows to compute 3 versions of the overlapping NMI
    LFK: The original implementation proposed by Lacichinetti et al.(1). The normalization of mutual information is done community by community
    MGH: In (2), McDaid et al. argued that the original NMI normalization was flawed and introduced a new (global) normalization by the max of entropy
    MGH_LFK: This is a variant of the LFK method introduced in (2), with the same type of normalization but done globally instead of at each community

    Results are checked to be similar to the C++ implementations by the authors of (2): https://github.com/aaronmcdaid/Overlapping-NMI

    :param cover: set of set of nodes
    :param coverRef:set of set of nodes
    :param allNodes:
    :param variant:
    :param adjustForChance:
    :return:

    :Reference:

    1. Lancichinetti, A., Fortunato, S., & Kertesz, J. (2009). Detecting the overlapping and hierarchical community structure in complex networks. New Journal of Physics, 11(3), 033015.
    2. McDaid, A. F., Greene, D., & Hurley, N. (2011). Normalized mutual information to evaluate overlapping community finding algorithms. arXiv preprint arXiv:1110.2515. Chicago

    """
    if (len(cover) == 0 and len(coverRef) != 0) or (
        len(cover) != 0 and len(coverRef) == 0
    ):
        return 0
    if cover == coverRef:
        return 1

    if allNodes is None:
        allNodes = {n for c in coverRef for n in c}
        allNodes |= {n for c in cover for n in c}

    if variant == "LFK":
        HXY = coverConditionalEntropy(cover, coverRef, allNodes, normalized=True)
        HYX = coverConditionalEntropy(coverRef, cover, allNodes, normalized=True)
    else:
        HXY = coverConditionalEntropy(cover, coverRef, allNodes)
        HYX = coverConditionalEntropy(coverRef, cover, allNodes)

    HX = coverEntropy(cover, allNodes)
    HY = coverEntropy(coverRef, allNodes)

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


import numpy as np
import numpy.linalg as la
import scipy.signal as scisig
import time

"""
https://github.com/jucaleb4/My_Work/blob/master/conv/Convolution/Winograd-Convolution.ipynb read before
"""

np.set_printoptions(linewidth=100)

B23 = np.asarray([
    [1, 0,-1, 0],
    [0, 1, 1, 0],
    [0,-1, 1, 0],
    [0, 1, 0,-1]
]).T

G23 = np.asarray([
    [ 1,  0, 0],
    [.5, .5,.5],
    [.5,-.5,.5],
    [ 0, 0,  1]
])

A23 = np.asarray([
    [1,1,1,0],
    [0,1,-1,-1]
]).T

### F(4x4,3x3)

n = 2**10

g = np.random.random(3)
d = np.random.random(n)
y = np.zeros(0)

d2 = np.append(np.zeros(2),d)
d2 = np.append(d2, np.zeros(2))
for i in range(len(d2)//2-1):
    yTemp = np.dot(A23.T, (G23 @ g) * (B23.T @ d2[i*2:(i*2)+4]) )
    y = np.append(y,yTemp)

convLib = np.convolve(d,g[::-1])

print("Error:",la.norm(y-convLib)/la.norm(convLib))

# assume N=1 image, K=1 filter, C=1 channel
def simpleWinogradAlg(g,d,m,B,G,A):
    N = K = C = 1
    """
    @g: 2d.numpy_array as square filter
    @d: 2d.numpy_array as data
    @m: int as output of FIR filter F(m,r)
    """
    h,w = d.shape
    r = g.shape[0]

    assert(g.shape[0] == g.shape[1])
    assert(h%m == 0 and w%m == 0)

    h-=m; w-=m

    P = (h//m)*(w//m) # num of tiles
    a = m+r-1 # input tile size

    dChunks = np.zeros((C,P,a,a))
    for c in range(C):
        for y in range(h//m):
            for x in range(w//m):
                b = y*(w//m) + x
                dChunks[c,b] = d[(y*m):(y*m)+a, (x*m):(x*m)+a]

    print(a,K,C)
    U = np.zeros((a,a,K,C))
    for k in range(K):
        for c in range(C):
            uInterm = np.dot(G, np.dot(g, G.T))
            for e in range(a):
                for v in range(a):
                    U[e,v,k,c] = uInterm[e,v]

    print(a,C,P)
    V = np.zeros((a,a,C,P))
    for b in range(P):
        for c in range(C):
            vInterm = np.dot(B.T, np.dot(dChunks[c,b], B))
            for e in range(a):
                for v in range(a):
                    V[e,v,c,b] = vInterm[e,v]

    M = np.zeros((a,a,K,P))
    for e in range(a):
        for v in range(a):
            M[e,v] = np.dot(U[e,v], V[e,v])

    Y = np.zeros((K,P,m,m))
    for k in range(K):
        for b in range(P):
            mInterm = np.zeros((a,a))
            for e in range(a):
                for v in range(a):
                    mInterm[e,v] = M[e,v,k,b]
            Y[k,b] = np.dot(A.T, np.dot(mInterm, A))

    Ynew = np.zeros((K,h,w))
    for k in range(K):
        for y in range(h//m):
            for x in range(w//m):
                b = y*(w//m) + x
                Ynew[k,y*m:(y+1)*m, x*m:(x+1)*m] = Y[k,b]
    return Ynew

def padImage(g,r):
    h,w = g.shape
    g2 = np.zeros((2*r-2 + h,2*r-2 + w))
    g2[r-1:r-1+h,r-1:r-1+w] = g
    return g2

def revMatrix(M):
    n1,n2 = M.shape
    return np.eye(n1)[::-1] @ M @ np.eye(n2)[::-1]

n = 2**7
r = 3
f = np.random.random((r,r))
g = np.random.random((n,n))
start = time.time()
c= scisig.convolve2d(f,g)
end = time.time()
print("Time elapsed 1D: ",end-start)

g2 = revMatrix(g)
g2 = padImage(g2,r)
startWino = time.time()
cWino = simpleWinogradAlg(f,g2,2,B23,G23,A23)[0]
endWino = time.time()
print("Time elapsed 2D : ",endWino-startWino)
cWino = revMatrix(cWino)

print("Error:",la.norm(c - cWino)/la.norm(c))

def simpleWinogradAlg_FLOPS(h,w,r,m,B,G,A,N,K,C,matmul):
    assert(h%m == 0 and w%m == 0)

    h-=2; w-=2

    P = (h//m)*(w//m) # num of tiles
    a = m+r-1 # input tile size

    dChunks = np.zeros((C,P,a,a))

    flops = np.zeros(2)

    U = np.zeros((a,a,K,C))
    g = np.zeros((r,r))
    temp = K * C * ( matmul(G,g) + matmul(g,G.T))
    flops += temp

    V = np.zeros((a,a,C,P))
    temp = N * P * C * ( matmul(B.T, dChunks[0,0]) + matmul(dChunks[0,0],B) )
    flops += temp

    M = np.zeros((a,a,K,P))
    # (K,C) x (C,P)
    temp = N * a * a * matmul(U[0,0],V[0,0])
    flops += temp

    Y = np.zeros((K,P,m,m))
    mInterm = np.zeros((a,a))
    temp = K * P * ( matmul(A.T, mInterm) + matmul(mInterm, A) )
    flops += temp

    # reorder
    return flops

def directMatmul(M1,M2):
    assert(M1.shape[1] == M2.shape[0])
    return M1.shape[0] * M1.shape[1] * M2.shape[1]

def direct2DConvFlops(C,N,K,H,W,R):
    return np.asarray([C*N*K*H*W*(R**2),2*C*N*K*H*W*(R**2)])

N = 1
K = 96 # higher means more savings for multiplications
C = 3

R = 11
M = 2
p = 8
H = M**p
W = M**p

a = M + R - 1
B = np.zeros((a,a)).T
G = np.zeros((a,R))
A = np.zeros((M,a)).T
# h,w,r,m,B,G,A,N,K,C,matmul
winoFlops = simpleWinogradAlg_FLOPS(H,W,R,M,B,G,A,N,K,C,directMatmul)
# C,N,K,H,W,R
directFlops = direct2DConvFlops(C,N,K,H,W,R)

lowest = H*W*K*N*C

print("Direct Flops:",directFlops)
print("Winograd Flops:",winoFlops)

print("Winograd Savings:",directFlops/winoFlops)


'''
velour -> datasets
(list of functions)

Generate datasets:
    - SampleOnCircle
    - SampleOnOlympics
    - SampleOnLemniscate
    - SampleOnSphere
    - SampleOnNecklace
    - SampleOnCircleNormalBundle
    - SampleOnCircleMobiusBundle
    - SampleOnTorusNormalBundle
    - SampleOnKleinBottleNormalBundle

Plotting utilities:
    - COLORS
    - set_axes_equal
    - PlotPointCloud
    - PlotPCA
    - PlotVectorField
    - PlotPersistenceDiagram
    - PlotPersistenceBarcodes
    - PlotLifebar
'''

import gudhi
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

from .geometry import VectorToProjection, ProjectionToVector

def SampleOnCircle(N_observation = 100, N_anomalous = 0):
    '''
    Sample N_observation points from the uniform distribution on the unit circle
    in R^2, and N_anomalous points from the uniform distribution on the unit square.

    Input:
        N_observation (int): number of sample points on the circle.
        N_noise (int, optional): number of sample points on the square.

    Output :
        data (np.array): size (N_observation + N_anomalous)x2, the points concatenated.
    '''
    rand_uniform = np.random.rand(N_observation)*2-1
    X_observation = np.cos(2*np.pi*rand_uniform)
    Y_observation = np.sin(2*np.pi*rand_uniform)
    X_anomalous = np.random.rand(N_anomalous)*2-1
    Y_anomalous = np.random.rand(N_anomalous)*2-1
    X = np.concatenate((X_observation, X_anomalous))
    Y = np.concatenate((Y_observation, Y_anomalous))
    data = np.stack((X,Y)).transpose()
    return data

def SampleOnOlympics(N_observation = 50, N_anomalous = 0):
    '''
    Sample 5*N_observation points from the uniform distribution on five unit
    circles in R^2, and N_anomalous points from the uniform distribution on
    a rectangle.

    Input:
        N_observation (int): number of sample points per circle.
        N_anomalous (int): number of sample points in rectangle.

    Output :
        data (np.array): size (5*N_observation+N_anomalous)x2, the sample points.
    '''
    l=1.25
    center1 = np.array([0,1])
    center2 = np.array([1*l,0])
    center3 = np.array([2*l,1])
    center4 = np.array([3*l,0])
    center5 = np.array([4*l,1])
    I = np.linspace(0, 2*np.pi, N_observation)

    circle = np.stack([np.cos(I),np.sin(I)])
    X1 = np.transpose(np.transpose(circle)+center1)
    X2 = np.transpose(np.transpose(circle)+center2)
    X3 = np.transpose(np.transpose(circle)+center3)
    X4 = np.transpose(np.transpose(circle)+center4)
    X5 = np.transpose(np.transpose(circle)+center5)
    XX = np.concatenate([X1,X2,X3,X4,X5], 1)
    XX = XX.T

    X_out = np.random.rand(N_anomalous)*(7*l-0.25)-1.75
    Y_out = np.random.rand(N_anomalous)*4-1.5
    YY = np.stack((X_out, Y_out)).T
    data = np.vstack((XX,YY))
    return data

def SampleOnLemniscate(N_observation = 100, N_anomalous = 50):
    '''
    Sample N_observation evenly spaced points on Bernouilli's lemniscate, and
    N_anomalous points from the uniform distribution on a rectangle.

    Input:
        N_observation (int): number of sample points per circle.
        N_anomalous (int): number of sample points in rectangle.

    Output :
        data (np.array): size (N_observation+N_anomalous)x2, the sample points.
    '''
    I = np.linspace(0, 2*np.pi, N_observation+1)
    I = I[0:-1]
    pas = 2*np.pi/N_observation

    a = np.sin(I)
    b = np.cos(I)*np.sin(I)
    c = (1+np.cos(I)**2)
    ap = np.cos(I)
    bp = np.cos(I)**2-np.sin(I)**2
    cp = -2*np.cos(I)*np.sin(I)
    Xp = (a*cp-ap*c)/(c**2)
    Yp = (b*cp-bp*c)/(c**2)

    g = np.sqrt(  ( Xp )**2 + ( Yp )**2)
    C=np.cumsum(g)
    G = np.concatenate(([0], C[0:-1]))*pas
    alpha = G[-1]
    II = np.linspace(0, alpha, N_observation)
    Ginv = np.interp(II, G, I)

    X_obs=np.sin(Ginv)/(1+np.cos(Ginv)**2)
    Y_obs=np.cos(Ginv)*np.sin(Ginv)/(1+np.cos(Ginv)**2)

    X_out = np.random.rand(N_anomalous)*2-1
    Y_out = np.random.rand(N_anomalous)-0.5
    X = np.concatenate((X_obs, X_out))
    Y = np.concatenate((Y_obs, Y_out))
    data = np.stack((X,Y)).transpose()

    return data

def SampleOnSphere(N_observation = 100, N_anomalous = 0):
    '''
    Sample N_observation points from the uniform distribution on the unit sphere
    in R^3, and N_anomalous points from the uniform distribution on the unit cube.

    Input:
        N_observation (int): number of sample points on the sphere.
        N_anomalous (int): number of sample points on the cube.

    Output:
        data (np.array): size (N_observation + N_anomalous)x3, the points concatenated.

    Example:
        X = SampleOnSphere(N_observation = 100, N_anomalous = 0)
        velour.PlotPointCloud(X)
    '''
    RAND_obs = np.random.normal(0, 1,  (3, N_observation))
    norms = np.sum(np.multiply(RAND_obs, RAND_obs).T, 1).T
    X_obs = RAND_obs[0,:]/np.sqrt(norms)
    Y_obs = RAND_obs[1,:]/np.sqrt(norms)
    Z_obs = RAND_obs[2,:]/np.sqrt(norms)
    X_out = np.random.rand(N_anomalous)*2-1
    Y_out = np.random.rand(N_anomalous)*2-1
    Z_out = np.random.rand(N_anomalous)*2-1
    X = np.concatenate((X_obs, X_out))
    Y = np.concatenate((Y_obs, Y_out))
    Z = np.concatenate((Z_obs, Z_out))
    data = np.stack((X,Y,Z)).transpose()
    return data

def SampleOnNecklace(N_observation = 100, N_anomalous = 0):
    '''
    Sample 4*N_observation points on a necklace in R^3, and N_anomalous points
    from the uniform distribution on a cube.

    Input :
        N_observation (int): number of sample points on the sphere.
        N_anomalous (int): number of sample points on the cube.

    Output :
        data (np.array): size (4*N_observation + N_anomalous)x3, the points concatenated .
    '''
    X1 = SampleOnSphere(N_observation, N_anomalous = 0)+[2,0,0]
    X2 = SampleOnSphere(N_observation, N_anomalous = 0)+[-1,2*.866,0]
    X3 = SampleOnSphere(N_observation, N_anomalous = 0)+[-1,-2*.866,0]
    X4 = 2*SampleOnCircle(N_observation, N_anomalous = 0)
    X4 = np.stack((X4[:,0],X4[:,1],np.zeros(N_observation))).transpose()
    data_obs = np.concatenate((X1, X2, X3, X4))
    X_out = 3*(np.random.rand(N_anomalous)*2-1)
    Y_out = 3*(np.random.rand(N_anomalous)*2-1)
    Z_out = 3*(np.random.rand(N_anomalous)*2-1)
    data_out =np.stack((X_out,Y_out,Z_out)).transpose()
    data = np.concatenate((data_obs, data_out))
    return data

def SampleOnCircleNormalBundle(N = 100, gamma=1, sd = 0):
    '''
    Returns a uniform N-sample of the normal bundle of the circle, with underlying
    norm with parameter gamma.
    If sd is nonzero, returns a noisy sample.

    Input:
        N (int): number of points to sample.
        gamma (float): parameter of the underlying norm (typically gamma=1).
        sd (float): standard deviation of the noise.

    Output:
        X_check: a (N x (2+2**2)) np.array, representing the vector bundle.

    Example:
        velour.SampleOnCircleNormalCheck(N = 2, gamma=1, sd=0)
        ---> array([[ 1.00000000e+00,  0.00000000e+00,  1.00000000e+00, 0.00000000e+00,  0.00000000e+00,  0.00000000e+00],
                    [ 1.00000000e+00, -2.44929360e-16,  1.00000000e+00, -2.44929360e-16, -2.44929360e-16,  5.99903913e-32]])
    '''
    U = np.linspace(0, 2*np.pi, N)
    Xdata = np.cos(U)
    Ydata = np.sin(U)
    XXnormal = gamma*np.cos(U)**2
    YYnormal = gamma*np.sin(U)**2
    XYnormal = gamma*np.cos(U)*np.sin(U)
    X_check = np.vstack((Xdata,Ydata, XXnormal, XYnormal, XYnormal, YYnormal)).T
    if sd:
        X_check = X_check + np.random.normal(loc = 0, scale = sd, size = np.shape(X_check))
    return X_check

def SampleOnCircleMobiusBundle(N = 100, gamma=1, sd = 0):
    '''
    Returns a uniform N-sample of the normal bundle of the circle, with underlying
    norm with parameter gamma.
    If sd is nonzero, returns a noisy sample.

    Input:
        N (int): number of points to sample.
        gamma (float): parameter of the underlying norm (typically gamma=1).
        sd (float): standard deviation of the noise.

    Output:
        X_check: a (N x (2+2**2)) np.array, representing the vector bundle.
    '''
    U = np.linspace(0, 2*np.pi, N+1)[:-1]
    Xdata = np.cos(U)
    Ydata = np.sin(U)
    XXnormal = gamma*np.cos(U/2)**2
    YYnormal = gamma*np.sin(U/2)**2
    XYnormal = gamma*np.cos(U/2)*np.sin(U/2)
    X_check = np.vstack((Xdata,Ydata, XXnormal, XYnormal, XYnormal, YYnormal)).T
    if sd:
        X_check = X_check + np.random.normal(loc = 0, scale = sd, size = np.shape(X_check))
    return X_check

def SampleOnTorusNormalBundle(N = 100, min_dist = 0.5, gamma=1):
    '''
    Returns a uniform sample of the normal bundle of the torus, seen in R^3,
    with underlying norm with parameter gamma. The initial number of points is N^2,
    and the sample is then sparsified with minimal distance between points being min_dist.

    Input:
        N (int): square root of the initial number of points to sample.
        min_dist (float): final minimal distance between points.
        gamma (floatà: parameter of the underlying norm (typically gamma=1).

    Output:
        X_check (np.array): size (N x (3+3**2)), representing the vector bundle.
        N_points (int): the number of final points.
    '''
    U = np.linspace(0, 2*np.pi, N)
    V = np.linspace(0, 2*np.pi, N)
    Xdata = np.zeros((N,N))
    Ydata = np.zeros((N,N))
    Zdata = np.zeros((N,N))
    X_normal = []

    for i in range(N):
        for j in range(N):
            u = U[i]
            v = V[j]
            x = (2+np.cos(u))*np.cos(v)
            y = (2+np.cos(u))*np.sin(v)
            z = np.sin(u)
            Xdata[i,j] = x
            Ydata[i,j] = y
            Zdata[i,j] = z
            normal = np.array([np.cos(v)*np.cos(u), np.sin(v)*np.cos(u), np.sin(u)])
            X_normal.append(normal)
    Xf = np.matrix.flatten(Xdata)
    Yf = np.matrix.flatten(Ydata)
    Zf = np.matrix.flatten(Zdata)
    X = np.vstack((Xf,Yf,Zf)).T

    X_map = {}
    for i in range(N**2):
        v = X_normal[i]
        A = VectorToProjection(v)
        X_map[i] = A
    X_check = np.zeros((N**2, 3+3*3))
    for i in range(N**2):
        X_check[i, 0:3] = X[i,:]
        X_check[i, 3:12] = np.reshape(gamma*X_map[i], (1,9))

    X_check = gudhi.subsampling.sparsify_point_set(points = X_check, min_squared_dist = min_dist**2)
    X_check = np.array(X_check)
    N_points = X_check.shape[0]
    print('The sample contains '+repr(N_points)+ ' points.', flush=True)

    return X_check, N_points

def SampleOnKleinBottleNormalBundle(N = 100, min_dist = 0.5, gamma=1):
    '''
    Returns a uniform sample of the normal bundle of the Klein bottle, seen in R^3,
    with underlying norm with parameter gamma. The initial number of points is N^2,
    and the sample is then sparsified with minimal distance between points being min_dist.

    Input:
        N (int): square root of the initial number of points to sample.
        min_dist (float): final minimal distance between points.
        gamma (float): parameter of the underlying norm (typicaly gamma=1).

    Output:
        X_check (np.array): size (N x (3+3**2)), representing the vector bundle.
        N_points (int): the number of final points.
    '''
    # Parameters of the Klein bottle
    a = 3
    b = 1

    U = np.linspace(0, 2*np.pi, N)
    V = np.linspace(0, 2*np.pi, N)
    Xdata = np.zeros((N,N))
    Ydata = np.zeros((N,N))
    Zdata = np.zeros((N,N))
    X_normal = []
    for i in range(N):
        for j in range(N):
            u = U[i]
            v = V[j]
            x = (a + b*(np.cos(u/2)*np.sin(v) - np.sin(u/2)*np.sin(2*v)))*np.cos(u)
            y = (a + b*(np.cos(u/2)*np.sin(v) - np.sin(u/2)*np.sin(2*v)))*np.sin(u)
            z = b*(np.sin(u/2)*np.sin(v) + np.cos(u/2)*np.sin(2*v))
            Xdata[i,j] = x
            Ydata[i,j] = y
            Zdata[i,j] = z
            #tangent vectors
            theta = u
            nu = v
            nx = np.array([ -1/2*(2*np.cos(nu)*np.cos(1/2*theta)*np.sin(nu) + b*np.sin(nu)*np.sin(1/2*theta))*np.cos(theta) - (b*np.cos(1/2*theta)*np.sin(nu) - 2*np.cos(nu)*np.sin(nu)*np.sin(1/2*theta) + a)*np.sin(theta),
                             (b*np.cos(1/2*theta)*np.sin(nu) - 2*np.cos(nu)*np.sin(nu)*np.sin(1/2*theta) + a)*np.cos(theta) - 1/2*(2*np.cos(nu)*np.cos(1/2*theta)*np.sin(nu) + b*np.sin(nu)*np.sin(1/2*theta))*np.sin(theta),
                             1/2*b*np.cos(1/2*theta)*np.sin(nu) - np.cos(nu)*np.sin(nu)*np.sin(1/2*theta) ])
            ny = np.array([ (b*np.cos(nu)*np.cos(1/2*theta) + 2*(2*np.sin(nu)**2 - 1)*np.sin(1/2*theta))*np.cos(theta),
                             (b*np.cos(nu)*np.cos(1/2*theta) + 2*(2*np.sin(nu)**2 - 1)*np.sin(1/2*theta))*np.sin(theta),
                             b*np.cos(nu)*np.sin(1/2*theta) + 2*(2*np.cos(nu)**2 - 1)*np.cos(1/2*theta) ])
            normal = np.cross(nx,ny)
            X_normal.append(normal)
    Xf = np.matrix.flatten(Xdata)
    Yf = np.matrix.flatten(Ydata)
    Zf = np.matrix.flatten(Zdata)
    X = np.vstack((Xf,Yf,Zf)).T

    X_map = {}
    for i in range(N**2):
        v = X_normal[i]
        A = VectorToProjection(v)
        X_map[i] = A
    X_check = np.zeros((N**2, 3+3*3))
    for i in range(N**2):
        X_check[i, 0:3] = X[i,:]
        X_check[i, 3:12] = np.reshape(gamma*X_map[i], (1,9))

    X_check = gudhi.subsampling.sparsify_point_set(points = X_check, min_squared_dist = min_dist**2)
    X_check = np.array(X_check)
    N_points = X_check.shape[0]
    print('The sample contains '+repr(N_points)+ ' points.', flush=True)

    return X_check, N_points

COLORS = ['red', 'green', 'blue', 'orange'] #colors of the persistence barcodes

def set_axes_equal(ax):
    '''
    Make axes of 3D plot have equal scale so that spheres appear as spheres,
    cubes as cubes, etc..  This is one possible solution to Matplotlib's
    ax.set_aspect('equal') and ax.axis('equal') not working for 3D.

    Input:
        ax (plt.axis): the axis.

    Remark:
        Copied from the anwser of karlo on https://stackoverflow.com/questions/13685386/matplotlib-equal-unit-length-with-equal-aspect-ratio-z-axis-is-not-equal-to
    '''
    x_limits = ax.get_xlim3d()
    y_limits = ax.get_ylim3d()
    z_limits = ax.get_zlim3d()
    x_range = abs(x_limits[1] - x_limits[0])
    x_middle = np.mean(x_limits)
    y_range = abs(y_limits[1] - y_limits[0])
    y_middle = np.mean(y_limits)
    z_range = abs(z_limits[1] - z_limits[0])
    z_middle = np.mean(z_limits)
    plot_radius = 0.5*max([x_range, y_range, z_range])
    ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
    ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
    ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])

def PlotPointCloud(X, theta1 = 50, theta2 = 75, plot_axis = False, values = []):
    '''
    Plot a point cloud in 2D or 3D. The input point cloud X has to have dimension
    N x n with n = 2 or 3.

    Input:
        X (np.array): size N x n, a cloud of N points.
        theta1, theta2 (float, optionnal): camera angle (only in 3D).
        plot_axis (bool): whether to plot the axis (only in 3D).
        values (np.array, optional): is nonempty, color the plot according to values.
    '''
    n = np.shape(X)[1]
    fig = plt.figure( figsize=(8,8) )
    if n==2:
        if not plot_axis:
            plt.axis('off')
        if len(values) != 0:
            plot = plt.scatter(X[:,0],X[:,1], lw = 3, c=values)
            plt.colorbar(plot)
        else:
            plt.scatter(X[:,0],X[:,1], color = 'black', lw = 3)
        plt.axis('equal')
    elif n==3:
        ax = fig.add_subplot(111, projection='3d')
        if len(values) != 0:
            plot =         ax.scatter3D(X[:,0], X[:,1], X[:,2], lw = 3, c = values)

            plt.colorbar(plot)
        else:
            ax.scatter3D(X[:,0], X[:,1], X[:,2], color = 'black', lw = 3)
        if not plot_axis:
            plt.axis('off')
        set_axes_equal(ax)
        ax.view_init(theta1, theta2)
        ax.xaxis.set_tick_params(labelsize=0)
        ax.yaxis.set_tick_params(labelsize=0)
        ax.zaxis.set_tick_params(labelsize=0)

def PlotPCA(X, theta1 = 50, theta2 = 75, plot_axis = True):
    '''
    Plot a point cloud in 3D after performing a Principal Component Analysis.

    Input:
        X (np.array): size N x (n+n**2), a cloud of N points.
        theta1, theta2 (float, optionnal): camera angle (only in 3D).
        plot_axis (bool): whether to plot the axis.
    '''
    pca = PCA(n_components=3)
    pca.fit(X)
    X_pca = pca.transform(X)
    X_pca = np.transpose(X_pca)
    fig = plt.figure( figsize=(10,10) )
    ax = fig.add_subplot(111, projection='3d')
    ax.scatter3D(X_pca[0, :], X_pca[1, :], X_pca[2, :], color = 'black', lw = 3)
    if not plot_axis:
        plt.axis('off')
    set_axes_equal(ax)
    ax.view_init(theta1, theta2)
    ax.xaxis.set_tick_params(labelsize=0)
    ax.yaxis.set_tick_params(labelsize=0)
    ax.zaxis.set_tick_params(labelsize=0)

def PlotVectorField(X, theta1 = 50, theta2 = 75, plot_axis = True):
    '''
    Plot a vector field in 2D or 3D. The input point cloud X has to have dimension
    N x (n+n**2), and n is interpreted as the dimension of the space.
    The last n**2 coordinates are interpreted as projection matrix onto a line.

    Input:
        X (np.array): size N x (n+n**2), a cloud of N points.
        theta1, theta2 (float, optionnal): camera angle (only in 3D).
        plot_axis (bool): whether to plot the axis (only in 3D).
    '''
    N_points = np.shape(X)[0]
    n=int((-1+np.sqrt(1+4*np.shape(X)[1]))/2) #get the integer n such that n+n**2=np.shape(X)[1]

    if n==2:
        l = .2   #arrows length
        fig = plt.figure( figsize=(10,10) )
        plt.scatter(X[:,0],X[:,1], color = 'black', lw = 3)
        for i in range(N_points):
            x = X[i,0:2]
            P = X[i,2:8]
            P = np.reshape(P, (2,2))
            v = ProjectionToVector(P)
            v = l*v
            plt.arrow(x[0], x[1], v[0], v[1], color = 'magenta',lw = 3)
            plt.arrow(x[0], x[1], -v[0], -v[1], color = 'magenta',lw = 3)
        plt.axis('off')
        plt.xlim((np.min(X[:,0])-l, np.max(X[:,0])+l))
        plt.ylim((np.min(X[:,1])-l, np.max(X[:,1])+l))

    if n==3:
        l = .5   #arrows length
        fig = plt.figure( figsize=(10,10) )
        ax = fig.add_subplot(111, projection='3d')
        ax.scatter3D(X[:,0], X[:,1], X[:,2], color = 'black', lw = 3)
        N = np.zeros((N_points,3))
        for i in range(N_points):
            P = X[i,3:13]
            P = np.reshape(P, (3,3))
            v = ProjectionToVector(P)
            N[i,:] = v
        ax.quiver(X[:,0], X[:,1], X[:,2], N[:,0], N[:,1], N[:,2], length=l, normalize=True, color = 'magenta')
        ax.quiver(X[:,0], X[:,1], X[:,2], -N[:,0], -N[:,1], -N[:,2], length=l, normalize=True, color = 'magenta')
        if not plot_axis:
            plt.axis('off')
        set_axes_equal(ax)
        ax.view_init(theta1, theta2)
        ax.xaxis.set_tick_params(labelsize=0)
        ax.yaxis.set_tick_params(labelsize=0)
        ax.zaxis.set_tick_params(labelsize=0)

def PlotPersistenceDiagram(st, homology_coeff_field = 2):
    '''
    Displays the persistence diagram of the simplex tree st.

    Input:
        st (gudhi.SimplexTree).
        homology_coeff_field (int, optional): Field of coefficients to compute the persistence
    '''
    gudhi.plot_persistence_diagram(st.persistence(homology_coeff_field = homology_coeff_field), colormap = tuple(COLORS))

def PlotPersistenceBarcodes(st, tmax = 1, d=2, eps = 0, hide_small_infinite_bars = False,
                            barcode_index = [],  homology_coeff_field = 2,
                            xtick = 0, xticks = []):
    '''
    Displays the persistence barcodes of the simplex tree st.

    Input:
        st (gudhi.SimplexTree).
        tmax (float, optional): maximal filtration value to plot the barcodes.
        d (int, optional): maximal dimension to compute the persistence.
        eps (float, optional): minimal length of displayed bars.
        hide_small_infinite_bars (bool, optional): if False, do not display bars of length < epsilon
                                         which exceed the maximal filtration value.
        barcode_index (list of int, optional): array containing the dimensions to plot the barcodes.
                                               By default: range(d+1).
        homology_coeff_field (int, optional): Field of coefficients to compute the persistence
        xticks (list of float, optional): a list of xticks. If nonempty, replace the default ticks by this list.
        xtick (bool, optional): if nonzero, add a xtick to the barcodes
    '''
    st.persistence(homology_coeff_field = homology_coeff_field)    #compute the persistence

    ' Plot the persistence barcodes '
    width = 0.75       #width of the bars
    eps1 = .1          #y margin
    eps2 = .0          #x margin
    if not barcode_index:
        barcode_index = range(d+1)
    for i in barcode_index:
        diagram = st.persistence_intervals_in_dimension(i)
        if hide_small_infinite_bars:
            diagram = [t for t in diagram if min(t[1], tmax)-t[0] > eps]          #select large enough bars
            diagram = [[t[0], min(t[1], tmax)] for t in diagram]                  #threshold the bars exceeding tmax
        else:
            diagram = [t for t in diagram if t[1]-t[0] > eps]                     #select large enough bars
            diagram = [[t[0], min(t[1], tmax)] for t in diagram]                  #threshold the bars exceeding tmax
        if diagram:
            color = COLORS[i]
            plt.figure( figsize=(10,2) )
            ax = plt.axes(frameon=True)
            for j in range(len(diagram)):
                t = diagram[j]
                plt.fill([t[0], t[1], t[1], t[0]], [j, j, j+width, j+width], fill=True, c=color, lw = 1.4)

            ax.set_xlim(-eps2, tmax+eps2)
            ax.spines['bottom'].set_visible(True)
            ax.spines['bottom'].set_bounds(0, tmax)
            ax.spines['bottom'].set_linewidth(1.5)
            if xticks:
                plt.xticks(xticks)
            else:
                if xtick:
                    plt.xticks([0, xtick, tmax])
                    ax.set_xticklabels([str(0), '%.1f' %xtick, '%.1f' %tmax])
                else:
                    plt.xticks([0, tmax])
                    ax.set_xticklabels([str(0), '%.1f' %tmax])
            ax.tick_params(axis='both', which='major', labelsize=20, size = 9, width = 2)
            ax.axes.get_yaxis().set_visible(False)
            ax.set_ylim(-eps1, len(diagram) + eps1)
            ax.spines['left'].set_visible(False)
            ax.spines['right'].set_visible(False)
            ax.spines['top'].set_visible(False)
            plt.title('H'+str(i)+'-barcode')

def PlotLifebar(Lifebar, filtration_max, plot_lifebar_curve = False):
    '''
    Plot the lifebar corresponding to the array Lifebar. The lifebar is a bar that
    is hatched until the value t_dagger, and then solid until the value filtration_max.
    The value t_dagger is defined as the first value for which I is nonzero.
    If plit_lifebar_curve == True, plot the actual computed lifebar, which can
    takes values different from 0 or 1 because of the non-simpliciality of the map.

    Input:
        Lifebar (np.array): size 1xN.
        filtration_max (float): the maximal filtration value.
        plot_lifebar_curve (bool)
    '''
    # Parameters of the lifebar
    width = 0.5   #width bar
    eps1 = 0.1   #ymargin
    eps2 = 0.02   #xmargin

    I = np.linspace(0, filtration_max, len(Lifebar))
    t_dagger = I[(Lifebar!=0).argmax()]
    plt.figure( figsize=(10,1) )
    ax = plt.axes(frameon=True)
    if t_dagger==0:
        plt.fill([0, filtration_max, filtration_max, 0], [0, 0, width, width], fill=False, c=COLORS[1], lw = 1.4, hatch = '/')
    else:
        plt.fill([0, t_dagger, t_dagger, 0], [0, 0, width, width], fill=False, c=COLORS[1], lw = 1.4, hatch = '/')
        plt.fill([t_dagger, filtration_max, filtration_max, t_dagger], [0, 0, width, width], fill=True, c=COLORS[1], lw = 1.4)
    ax.set_xlim(-eps2, filtration_max+eps2)
    ax.spines['bottom'].set_visible(True)
    ax.spines['bottom'].set_bounds(-eps2, filtration_max+eps2)
    ax.spines['bottom'].set_linewidth(2)
    ax.tick_params(axis='both', which='major', labelsize=20, size = 9, width = 2)
    ax.axes.get_yaxis().set_visible(False)
    ax.set_ylim(-eps1, width + eps1)
    ax.spines['left'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.spines['top'].set_visible(False)

    if plot_lifebar_curve:
        ax.plot(I,Lifebar*width, lw=5, c='black')  #plot of the actual computed lifebar,
                                                   #which can takes values different from 0 or 1
                                                   #because of the non-simpliciality of g


#!/usr/bin/python3
import numpy as np
import matplotlib.pyplot as plt

def solver(I, w, dt, T):
    dt = float(dt)
    Nt = int(round(T/dt))
    u = np.zeros(Nt+1)
    t = np.linspace(0, Nt*dt, Nt+1)
    u[0] = I
    u[1] = u[0] - 0.5*dt**2*w**2*u[0]
    for n in range(1, Nt):
        u[n+1] = 2*u[n] - u[n-1] - dt**2*w**2*u[n]
    return u, t

def u_exact(t, I, w):
    return I*np.cos(w*t)

def visualize(u, t, I, w, dt):
    plt.plot(t, u, 'r--o')
    t_fine = np.linspace(0, t[-1], 1001) # very fine mesh for u_e
    u_e = u_exact(t_fine, I, w)
    plt.hold('on')
    plt.plot(t_fine, u_e, 'b-')
    plt.legend(['numerical', 'exact'], loc='upper left')
    plt.xlabel('t')
    plt.ylabel('u')
    dt = t[1] - t[0]
    plt.title('dt=%g' % dt)
    umin = 1.2*u.min()
    umax = -umin
    plt.axis([t[0], t[-1], umin, umax])
    plt.savefig('tmp1.png')
    plt.savefig('tmp1.pdf')


import numpy as np

def convolute_image(im, kernel, operation):
    h,w = im.shape

    kernel_h, kernel_w = kernel.shape

    even_kernel = kernel_h%2 == 0
    kernel_h_off, kernel_w_off = kernel_h//2,  kernel_w//2

    resize_im = np.pad(im, (kernel_h_off,kernel_w_off), mode='edge')

    im_out = np.zeros((h,w))

    for i in range(h):
        for j in range(w):
            if even_kernel:
                i_off = i+kernel_h_off+1
                j_off = j+kernel_w_off+1
            else:
                i_off = i+2*kernel_h_off+1
                j_off = j+2*kernel_w_off+1
            portion = resize_im[
                i: i_off,
                j: j_off
            ]
            im_out[i,j] = operation(portion, kernel)

    return im_out

def kernel_sum(portion, kernel):
    return np.sum(kernel*portion)

def low_pass(im, order=1):
    kernel = 1/9 * np.ones((2*order+1, 2*order+1))

    return convolute_image(im,kernel,kernel_sum)

def high_pass(im, alpha=1, order=1):
    kernel = 1/9 * np.array([
        [-1,-1,-1],
        [-1, 8,-1],
        [-1,-1,-1]
    ])

    return (im+convolute_image(im, kernel, kernel_sum)).clip(0,255)

def sharpening(im):
    kernel = np.array([
        [0   ,-1/4,    0],
        [-1/4,   2, -1/4],
        [0   ,-1/4,    0],
    ])

    return convolute_image(im, kernel, kernel_sum).clip(0,255)

def median_filter(im, order=1):
    kernel = np.ones((2*order+1, 2*order+1))
    def median (portion, kernel):
        return np.median(portion)
    return convolute_image(im, kernel, median)

def roberts(im, g):
    kernel = np.array([
        [1, 0],
        [0,-1]
    ])
    if g=='y':
        kernel = np.rot90(kernel)
    elif g!='x':
        raise ValueError('The parameter g hast to be "x" or "y"')

    return np.clip(convolute_image(im, kernel, kernel_sum),0,255)

def prewitt(im, g):
    kernel = np.array([
        [-1,-1,-1],
        [ 0, 0, 0],
        [ 1, 1, 1]
    ])

    if g=='y':
        kernel = np.transpose(kernel)
    elif g!='x':
        raise ValueError('The parameter g hast to be "x" or "y"')

    return np.clip(convolute_image(im, kernel, kernel_sum),0,255)

def sobel(im, g):
    kernel = np.array([
        [-1,-2,-1],
        [ 0, 0, 0],
        [ 1, 2, 1]
    ])

    if g=='y':
        kernel = np.transpose(kernel)
    elif g!='x':
        raise ValueError('The parameter g hast to be "x" or "y"')

    return np.clip(convolute_image(im, kernel, kernel_sum),0,255)

def laplace(im):
    kernel = np.array([
        [0,1,0],
        [1,-4,1],
        [0,1,0]
    ])
    return convolute_image(im, kernel, kernel_sum)

def fft_image(im):
    unshift_fft = np.fft.fft2(im)
    fft = np.fft.fftshift(unshift_fft)

    return fft

def ifft_image(fft):
    unshift = np.fft.ifftshift(fft)
    return np.abs(np.fft.ifft2(unshift))

def distance(p1, p2):
    x1,y1 = p1[:2]
    x2,y2 = p2[:2]

    return np.sqrt((x1-x2)**2 + (y1-y2)**2)

def low_pass_ideal(p, center, D0):
    if distance(p,center)<=D0:
        return 1
    else:
        return 0

def low_pass_butterworth(p, center, D0, **kwargs):
    n = kwargs['n']
    return 1/(1+(distance(p,center)/D0)**(2*n))

def low_pass_gaussian(p, center, D0):
    return np.exp(((-distance(p,center)**2)/(2*(D0**2))))

low_pass_f ={
    'ideal': low_pass_ideal,
    'butterworth':low_pass_butterworth,
    'gaussian':low_pass_gaussian
}

def high_pass_ideal(p, center, D0):
    if distance(p,center)<=D0:
        return 0
    else:
        return 1

def high_pass_butterworth(p, center, D0, **kwargs):
    n = kwargs['n']
    return 1-1/(1+(distance(p,center)/D0)**(2*n))

def high_pass_gaussian(p, center, D0):
    return 1 - np.exp(((-distance(p,center)**2)/(2*(D0**2))))

high_pass_f ={
    'ideal': high_pass_ideal,
    'butterworth':high_pass_butterworth,
    'gaussian':high_pass_gaussian
}

def freq_mask(D0, im_shape,fs,shape='ideal', **kwargs):
    rows, cols =im_shape[:2]
    base = np.ones((rows,cols))
    center = (rows/2, cols/2)

    for i in range(rows):
        for j in range(cols):
            base[i,j] = fs[shape]((i,j), center, D0, **kwargs)

    return base

def low_pass_mask(D0, im_shape, shape='ideal', **kwargs):
    return freq_mask(D0, im_shape, low_pass_f, shape, **kwargs)

def high_pass_mask(D0, im_shape, shape='ideal', **kwargs):
    return freq_mask(D0, im_shape, high_pass_f, shape, **kwargs)

def apply_freq_filter(im, mask):
    fft_im = fft_image(im)

    filtered = fft_im*mask

    return ifft_image(filtered)

def lp_ideal(im, D0=100):
    return apply_freq_filter(im, low_pass_mask(D0, im.shape, shape='ideal'))
def lp_butterworth(im, D0=100,n=2):
    return apply_freq_filter(im, low_pass_mask(D0, im.shape, shape='butterworth',n=n))
def lp_gaussian(im, D0=100):
    return apply_freq_filter(im, low_pass_mask(D0, im.shape, shape='gaussian'))
def hp_ideal(im, D0=100):
    return apply_freq_filter(im, high_pass_mask(D0, im.shape, shape='ideal'))
def hp_butterworth(im, D0=100,n=2):
    return apply_freq_filter(im, high_pass_mask(D0, im.shape, shape='butterworth',n=n))
def hp_gaussian(im, D0=100):
    return apply_freq_filter(im, high_pass_mask(D0, im.shape, shape='gaussian'))


import tensorflow as tf
import numpy as np

def get_angles(pos, i, d_model):
    angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))
    return pos * angle_rates

def positional_encoding(position, d_model):
    angle_rads = get_angles(np.arange(position)[:, np.newaxis],
                            np.arange(d_model)[np.newaxis, :],
                            d_model)
    # apply sin to even indices in the array; 2i
    angle_rads[:, 0::2] = np.sin(angle_rads[:, 0::2])
    # apply cos to odd indices in the array; 2i+1
    angle_rads[:, 1::2] = np.cos(angle_rads[:, 1::2])
    pos_encoding = angle_rads[np.newaxis, ...]

    return tf.cast(pos_encoding, dtype=tf.float32)

def create_padding_mask(seq):
    seq = tf.cast(tf.math.equal(seq, 0), tf.float32) # returns 0 and 1 float
    # add extra dimensions to add the padding
    # to the attention logits.
    return seq[:, tf.newaxis, tf.newaxis, :]  # (batch_size, 1, 1, seq_len)

# def create_padding_mask_np(seq):
#     seq = np.equal(seq, 0).astype(dtype=np.float32)
#     return seq[:, np.newaixs, np.newaxis, :]

def create_look_ahead_mask(size):
    mask = 1 - tf.linalg.band_part(tf.ones((size, size)), -1, 0) # select band diagonal -> lower this case
    return mask  # (seq_len, seq_len)

# def create_look_ahead_mask_np(size):
#     mask = 1 - np.tril(np.ones(size, size), 0)
#     return mask  # (seq_len, seq_len)

def point_wise_feed_forward_network(d_model, dff):
    """point wise nn because if u have (seq_length, hidden) at each seq_length you multiple
        by a the same dense layer. this is the default behaviour of a dense layer in keras anyways """
    return tf.keras.Sequential([
        tf.keras.layers.Dense(dff, activation='relu'),  # (batch_size, seq_len, dff)
        tf.keras.layers.Dense(d_model)  # (batch_size, seq_len, d_model)
    ])

def create_masks(inp, tar):
    # Encoder padding mask
    enc_padding_mask = create_padding_mask(inp)

    # Used in the 2nd attention block in the decoder.
    # This padding mask is used to mask the encoder outputs.
    dec_padding_mask = create_padding_mask(inp)

    # Used in the 1st attention block in the decoder.
    # It is used to pad and mask future tokens in the input received by
    # the decoder.
    look_ahead_mask = create_look_ahead_mask(tf.shape(tar)[1])
    dec_target_padding_mask = create_padding_mask(tar)
    combined_mask = tf.maximum(dec_target_padding_mask, look_ahead_mask)

    return enc_padding_mask, combined_mask, dec_padding_mask


import math
import matplotlib.pyplot as plt
import numpy as np
import sympy as sym
from sympy.utilities.lambdify import lambdify

def stima_ordine(xk, num):
    k = num - 4
    num = np.log(abs(xk[k + 3] - xk[k + 2]) / abs(xk[k + 2] - xk[k + 1]))
    den = np.log(abs(xk[k + 2] - xk[k + 1]) / abs(xk[k + 1] - xk[k]))
    return num / den

def newton(f, df, m, x0, tolx, tolf, nmax):
    def delta(val): return f(val) / df(val) if abs(df(val)) > np.spacing(1) else print("DERIVATA NULLA")
    def prossimax(val): return val - delta(val) * m

    x = prossimax(x0)
    fx = f(x)
    it, xk = 1, [x,]
    while it < nmax and abs(fx) > tolf and abs(delta(x)) > tolx * abs(x):
        x = prossimax(x)
        xk.append(x)
        fx = f(x)
        it += 1

    return x, it, xk

x = sym.symbols('x')
fx = x - (1/3) * sym.sqrt(30*x - 25)
dfx = sym.diff(fx, x, 1)

f = lambdify(x, fx, np)
df = lambdify(x, dfx, np)

a = 5 / 6
b = 25 / 6

xs = np.linspace(a, b, 100)

xz = xs * 0
plt.plot(xs, xz)
plt.plot(xs, f(xs))

## Calcolo lo zero
alpha, it, xk = newton(f, df, 1, a, 1e-18, 1e-18, 1_000)
print(f"Lo zero è {alpha}, it = {it}")

# Punto C
ordine = stima_ordine(xk, it)
print(f"L'ordine è {ordine}")
plt.show()

### Punto D
plt.semilogy(range(it), xk)


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

def mean(list_provided):
    return sum(list_provided)/len(list_provided)

def graph_fit(funcToUse, xData, yData, save_it = False, save_path = '.\img.png', x_min=False, y_min = False, y_max=False, x_max=False,x_step = 0.01):

    if x_min and x_max and y_min and y_max :
        pass
    else:
        m = min(xData)
        M = max(xData)
        avg = mean(xData)
        diff = m-m
        x_min = m - (diff/avg)
        x_max = M + (diff/avg)
        m = min(yData)
        M = max(yData)
        avg = mean(yData)
        diff = m-m
        y_min = m - (diff/avg)
        y_max = M + (diff/avg)

    popt, pcov = curve_fit(funcToUse,xData,yData)

    xFit = np.arange(x_min,x_max,x_step)

    plt.plot(xFit,funcToUse(xFit, *popt))
    plt.plot(xData,yData,'r*')

    if save_it:
        plt.savefig(save_path)

def auto_fit(xData, yData,x_step=False):

    def func1(x,a,b):
        return a*np.exp(b*x)

    def func2(x,a,b):
        return a*(x**b)

    def func3(x,a,b):
        return a*np.sin(b*x)

    def func4(x,a,b):
        return a*np.cos(b*x)

    def func5(x,a,b):
        return a + b*x

    def func6(x,a,b):
        return a + b/x

    def func7(x,a,b,c):
        return a + b*x + c*x*x

    def func8(x,a,b,c,d):
        return a + b*x + c*x*x + d*x*x*x

    def func9(x,a,b,c,d):
        return a+ b*x + c/x + d*x*x

    func_list = [func1,func2,func3,func4,func5,func6,func7,func8,func9]

    try:

        x_min = min(xData)
        x_max = max(xData)

        y_min = min(yData)
        y_max = max(yData)

        if not x_step:
            x_step = (x_max-x_min)/100

        dd = {}

        for f in func_list:

            popt, pcov = curve_fit(f,xData,yData,maxfev=10000)

            dis_rms = 0
            for i in range(len(xData)):
                y4 = yData[i]
                x4 = xData[i]
                y44 = f(x4,*popt)
            dis_rms += (y4-y44)**2
            dd[dis_rms]=f

        funcToUse = dd[min(dd)]

        popt, pcov = curve_fit(funcToUse,xData,yData,maxfev=10000)

        xFit = np.arange(x_min,x_max,x_step)

        plt.plot(xFit,funcToUse(xFit, *popt))
        plt.plot(xData,yData,'r.')
        plt.title(str(funcToUse).split()[1][-6:])
        plt.show()
    except:
        plt.plot(xData,yData)
        plt.title("normal")
        plt.plot(xData,yData,'r.')
        plt.show()


""" Example of ordinary Monte Carlo random sampling integrating circle area """
import numpy as np
from pylab import *; ion()
import probayes as pb

# PARAMETERS
radius = 1.
set_size = {-10000}

# SETUP CIRCLE FUNCTION AND RVs
def inside(o, x, y):
  return o == (x**2 + y**2 <= radius**2)

xy_range = [-radius, radius]
x = pb.RV("x", xy_range)
y = pb.RV("y", xy_range)
o = pb.RV("o")

# DEFINE STOCHASTIC CONDITION
xy = x & y
oxy = o | xy
oxy.set_prob(inside)

# CALL PROBABILITIES AND EVALUATE EXPECTATION AND AREA
p_cond_omc = oxy({'x,y': set_size})
p_omc_true = p_cond_omc({'o': True})
xy_vals = p_cond_omc('cond')
p_marg_omc = xy(xy_vals)
p_joint_omc = p_marg_omc * p_cond_omc
joint_expt = p_joint_omc.expectation()
square_area = 4. * radius**2
circle_area = square_area * joint_expt['o']

# PLOT DATA FROM CONDITIONAL DISTRIBUTION
figure()
true = np.nonzero(p_omc_true.prob)[0]
false = np.nonzero(np.logical_not(p_omc_true.prob))[0]
x_all, y_all = p_omc_true['x'], p_omc_true['y']
plot(x_all[true], y_all[true], 'b.')
plot(x_all[false], y_all[false], 'r.')
xlabel('x')
ylabel('y')
title('Est. circle area={}'.format(circle_area))


import numpy as np

class Regularizer(object):
    """Basic Regularizer class."""

    def __init__(self, reg=0, include_bias=True):
        super().__init__()
        self._lambda = reg
        self.include_bias = include_bias

    def set_lambda(self, reg):
        self._lambda = reg

    def get_lambda(self):
        return self._lambda

    def loss(self, w):
        return 0

    def gradient(self, w):
        return np.zeros_like(w)

class L2Regularizer(Regularizer):
    """L2Regularizer.

    L2 regularization is given by lambda @ w @ w.
    """

    def __init__(self, reg=0, include_bias=True):
        super().__init__(reg, include_bias)

    def loss(self, w):
        if self.include_bias:
            return self._lambda * np.square(np.linalg.norm(w[:-1], 2))
        return self._lambda * np.square(np.linalg.norm(w, 2))

    def gradient(self, w):
        if self.include_bias:
            gradient = np.zeros_like(w)
            gradient[:-1] = self._lambda * w[:-1]
        else:
            gradient = self._lambda * w
        return gradient

class L1Regularizer(Regularizer):
    """L1Regularizer.

    L1 regularization is given by lambda @ |w|.
    """

    def __init__(self, reg=0, include_bias=True):
        super().__init__(reg, include_bias)

    def loss(self, w):
        if self.include_bias:
            return self._lambda * np.linalg.norm(w[:-1], 1)
        return self._lambda * np.linalg.norm(w, 1)

    def gradient(self, w):
        if self.include_bias:
            gradient = np.zeros_like(w)
            gradient[:-1] = self._lambda * np.sign(w[:-1])
        else:
            gradient = self._lambda * np.sign(w)
        return gradient


import numpy
from matplotlib import pyplot
from matplotlib import cm

## Variable declaration
# parameters
nu = .1

# time
tmax = 0.5
nt = 151
dt = tmax/(nt-1)

# mesh
nx = 151
xmin = 0
xmax = 2*numpy.pi
dx = (xmax-xmin)/(nx-1)
x = numpy.linspace(xmin, xmax, nx)

# Initialize dependent variable
u = numpy.zeros(nx)
up = numpy.zeros(nx)    # from previous time-step

# Initial condition: saw tooth
phi = numpy.exp(-x**2/(4*nu)) + numpy.exp(-(x-2*numpy.pi)**2/(4*nu))
dphi = - 0.5*x/nu * numpy.exp(-x**2/(4*nu)) - 0.5*(x-2*numpy.pi)/nu * numpy.exp(-(x-2*numpy.pi)**2/(4*nu))

u = -2*nu*dphi/phi + 4

# Numerical solution
def anal_sol(t):
    phi2 = numpy.exp(-(x-4*t)**2/(4*nu*(t+1))) + numpy.exp(-(x-4*t-2*numpy.pi)**2/(4*nu*(t+1)))
    dphi2 = - 0.5*(x-4*t)/(nu*(t+1)) * numpy.exp(-(x-4*t)**2/(4*nu*(t+1))) - 0.5*(x-4*t-2*numpy.pi)/(nu*(t+1)) *\
            numpy.exp(-(x-4*t-2*numpy.pi)**2/(4*nu*(t+1)))
    u_anal = -2*nu*dphi2/phi2 + 4

    return u_anal

def advance(t_steps):
    for n in range(0,t_steps): # loop over all time-steps
        up = u.copy()
        # with periodic BC all are internal points
        u[1:-1] = up[1:-1] - up[1:-1]*dt/dx*(up[1:-1] - up[0:-2]) + \
                nu*dt/dx**2*(up[2:]-2*up[1:-1]+up[0:-2])

        u[0] = up[0] - up[0]*dt/dx*(up[0] - up[-1]) + \
                nu*dt/dx**2*(up[1]-2*up[0]+up[-1])
        u[-1] = up[-1] - up[-1]*dt/dx*(up[-1] - up[-2]) + \
                nu*dt/dx**2*(up[0]-2*up[-1]+up[-2])

def plot_multi(x, u, incr):
    fig = pyplot.figure(figsize=(9, 5), dpi=100)
    ax = pyplot.subplot(111)
    colour = iter(cm.rainbow(numpy.linspace(0,incr,nt)))
    for i in range(0, nt, incr):
        c = next(colour)
        ax.plot(x, u, label='i='+str(i)+' numerical')
        ax.plot(x, anal_sol(i*dt), 'ko', c=c, markerfacecolor='none', label='i='+str(i)+' analytical')
        box = ax.get_position()
        ax.set_position([box.x0, box.y0, box.width*0.97, box.height])
        legend = ax.legend( bbox_to_anchor=(1.02,1), loc=2, fontsize=12)
        advance(incr)
        pyplot.xlabel('x (m)')
        pyplot.ylabel('u (m/s)')
        pyplot.xlim(0, 2*numpy.pi)
        pyplot.title('Burgers equation')
    pyplot.show()

# Let us plot some curves to check if everything is alright
plot_multi(x, u, 20)


import numpy as np
import torch

def softmax(tensor: torch.Tensor, dim=-1):
    max_values, indices = torch.max(tensor, dim=dim, keepdim=True)
    exp = torch.exp(tensor - max_values)
    exp_sum = torch.sum(exp, dim=dim, keepdim=True)
    return torch.divide(exp, exp_sum)

def positional_encoding(max_position_encoding, d_model):
    positions = np.arange(max_position_encoding)[:, np.newaxis]  # (max_position_encoding, 1)
    dimensions = np.arange(d_model)[np.newaxis, :]  # (1, d_model)
    # (max_position_encoding, d_model)
    angles = positions / np.power(10000, (2 * (dimensions // 2)) / np.float32(d_model))

    # Apply sin to even indices
    angles[0::2] = np.sin(angles[0::2])
    # Apply cos to odd indices
    angles[1::2] = np.cos(angles[1::2])

    encoding = angles[np.newaxis, ...]  # (1, max_position_encoding, d_model)

    return torch.from_numpy(encoding)

def create_padding_mask(x):
    """
    Creates a mask to denote padding locations.
    The mask contains value 1.0 if the value in the given
    sequence is zero. Other values are set to 0.

    :param x: A sequence of shape (batch_size, seq_len)
    :return: A padding mask of shape (batch_size, 1, 1, seq_len)
    """
    mask = (x == 0.).type(x.dtype)
    return mask[:, None, None, :]