"""
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)))


# binarysearch.py
"""Use binary search to locate an item in an array."""
import numpy as np

def binary_search(data, key):
    """Perform binary search of data looking for key."""
    low = 0   # low end of search area
    high = len(data) - 1  # high end of search area
    middle = (low + high + 1) // 2  # middle element index
    location = -1  # return value -1 if not found

    # loop to search for element
    while low <= high and location == -1:
        # print remaining elements of array
        print(remaining_elements(data, low, high))

        print('   ' * middle, end='')  # output spaces for alignment
        print(' * ')  # indicate current middle

        # if the element is found at the middle
        if key == data[middle]:
            location = middle  # location is the current middle
        elif key < data[middle]:  # middle element is too high
            high = middle - 1  # eliminate the higher half
        else:  # middle element is too low
            low = middle + 1  # eliminate the lower half

        middle = (low + high + 1) // 2  # recalculate the middle

    return location  # return location of search key

def remaining_elements(data, low, high):
    """Display remaining elements of the binary search."""
    return '   ' * low + ' '.join(str(s) for s in data[low:high + 1])

def main():
    # create and display array of random values
    data = np.random.randint(10, 91, 15)
    data.sort()
    print(data, '\n')

    search_key = int(input('Enter an integer value (-1 to quit): '))

    # repeatedly input an integer; -1 terminates the program
    while search_key != -1:
        location = binary_search(data, search_key)  # perform search

        if location == -1:  # not found
            print(f'{search_key} was not found\n')
        else:
            print(f'{search_key} found in position {location}\n')

        search_key = int(input('Enter an integer value (-1 to quit): '))

# call main if this file is executed as a script
if __name__ == '__main__':
    main()

##########################################################################
# (C) Copyright 2019 by Deitel & Associates, Inc. and                    #
# Pearson Education, Inc. All Rights Reserved.                           #
#                                                                        #
# DISCLAIMER: The authors and publisher of this book have used their     #
# best efforts in preparing the book. These efforts include the          #
# development, research, and testing of the theories and programs        #
# to determine their effectiveness. The authors and publisher make       #
# no warranty of any kind, expressed or implied, with regard to these    #
# programs or to the documentation contained in these books. The authors #
# and publisher shall not be liable in any event for incidental or       #
# consequential damages in connection with, or arising out of, the       #
# furnishing, performance, or use of these programs.                     #
##########################################################################


#Importing the required libraries
import numpy as np
import matplotlib.pyplot as plt

#Creating a new figure
plt.figure()

#Initializing x and y values
x_values = np.arange(-10, 10, 0.01)
y_values = 1 / (1 + np.exp(-x_values))

#Plotting the values of x and y in the output
plt.plot(x_values, y_values, '-g', label = 'y=-log(x)', lw = 2)

#Annotating the graph
plt.annotate('y=1/(1+e^(-x))', xy=(0, 0.5), xytext=(2.5, 0.4),
            arrowprops = dict(facecolor='black', shrink=0.05))

#Inserting proper labels and title
plt.xlabel('X-Axis')
plt.ylabel('Y-Axis')
plt.title('Question-1(d)')

#Displaying the graph in the output
plt.show()


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


import numpy as np
import pandas as pd
from scipy.io import arff
import matplotlib.pyplot as plt

# 定义异常类
class DimensionValueError(ValueError):
    pass

# 读入数据
def loadArffData(path):
    data = arff.loadarff(path)
    df = pd.DataFrame(data[0])
    return df

def Predata(path):
    df = loadArffData(path)
    labels = df.values[:, -1]
    labels = labels == b'tested_positive'

    data = df.values[:, 0:df.values.shape[-1] - 1].T
    return data, labels

def datasplit(data, labels):

    tmp = np.hstack((labels.reshape((-1, 1)), data))
    df = pd.DataFrame(tmp, columns=['lable', 'x', 'y'])

    data1 = df.loc[df.lable == True]
    data2 = df.loc[df.lable == False]
    print(data1)
    print(data2)
    return data1, data2

#图示
def showInImg(data, labels):

    data1, data2 = datasplit(data, labels)
    # data1 = pd.DataFrame(data1, columns=['x', 'y'])
    # data2 = pd.DataFrame(data2, columns=['x', 'y'])

    #分别画出scatter图，但是设置不同的颜色
    plt.scatter(data1['x'], data1['y'], color='blue', label='negative')
    plt.scatter(data2['x'], data2['y'], color='green', label='positive')

    #设置图例
    plt.legend(loc=(1, 0))

    #显示图片
    plt.show()

# 直接计算

class PCA(object):

    def __init__(self, x,  d_dimension = 2):
        self.x = x
        self.dimension = x.shape[0]

        if d_dimension > self.dimension:
            raise DimensionValueError("dimension error")

        self.ddimension = d_dimension

    def Covariance(self):
        return np.cov(self.x.astype(np.float32)) #一条记录为一行
        # return np.cov(self.x.T) #一条记录为一列

    def SortEig(self):
        cov = self.Covariance()
        eigvalue, eigvector = np.linalg.eig(cov)
        eigvector = np.transpose(eigvector)
        tmp = np.hstack((eigvalue.reshape((eigvalue.shape[0], 1)), eigvector))
        tmp = pd.DataFrame(tmp)
        res = tmp.sort_values(by=0, ascending=False)
        return res

    def dimensionReductionBydimension(self):
        P = self.SortEig().values[:self.ddimension, 1:]

        res = P.dot(self.x)
        return res.T

# 0均值化后降维

def pca(input_x, d_dimension = 2):
    row, column = input_x.shape

    #0均值化
    mean = np.mean(input_x, axis=-1)
    mean = np.array(list(mean) * column).reshape(column, row).T
    input_x = input_x.astype(np.float64) - mean

    Covariance = input_x.dot(input_x.T)
    # print(Covariance.shape)
    # Covariance = np.cov(np.transpose(input_x))

    eigvalue, eigvector = np.linalg.eig(Covariance)
    eigvector = np.transpose(eigvector)
    tmp = np.hstack((eigvalue.reshape((eigvalue.shape[0], 1)), eigvector))
    tmp = pd.DataFrame(tmp)
    res = tmp.sort_values(by=0, ascending=False)

    P = res.values[:d_dimension, 1:]
    result = P.dot(input_x)
    return result.T

if __name__ == "__main__":

    data, labels = Predata('diabetes.arff')

    # print(pca(input_x=data,d_dimension = 2))
    showInImg(pca(input_x=data,d_dimension = 2), labels)

    x = PCA(x=data, d_dimension=2)
    # print(x.dimensionReductionBydimension())
    showInImg(x.dimensionReductionBydimension(), labels)


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


from xicam.core.intents import ImageIntent
from xicam.plugins.operationplugin import operation, output_names, display_name, describe_input, describe_output, \
    categories, intent, visible
import numpy as np
import scipy.misc
import scipy.fft

#region "Easy"-difficulty wrapping
# random_array = operation(np.random.random)
#endregion

# region "Medium"-difficulty wrapping
@operation
@output_names("data")
@display_name("Random Array")
@categories(("General", "Mathematics"))
def random_array(rows: int = 10, columns: int = 10) -> np.ndarray:
    return np.random.random((rows, columns))

@operation
@output_names("data")
@display_name("Raccoon Face")
@categories(("General", "Synthetic Data"))
@intent(ImageIntent, 'Raccoon Face', output_map={'image': 'data'})
def raccoon_face(gray: bool = True) -> np.ndarray:
    return scipy.misc.face(gray=gray)

@operation
@output_names("spectral_data")
@display_name("Fourier Transform")
@categories(("General", "Mathematics"))
@intent(ImageIntent, 'Fourier Transform', output_map={'image': 'spectral_data'})
@visible('data', False)
def fourier_transform(data: np.ndarray) -> np.ndarray:
    return scipy.fft.fft2(data)

@operation
@output_names("spectral_data")
@display_name("Low Band Pass Filter")
@intent(ImageIntent, 'Low Band Pass Filter', output_map={'image': 'spectral_data'})
@categories(("General", "Mathematics"))
@visible('spectral_data', False)
def low_band_pass(spectral_data: np.ndarray, keep_fraction:float=.1)->np.ndarray:
    shape = spectral_data.shape

    # Set to zero all rows with indices between r*keep_fraction and shape[0]*(1-keep_fraction):
    spectral_data[int(shape[0] * keep_fraction):int(shape[0] * (1 - keep_fraction))] = 0

    # Similarly with the columns:
    spectral_data[:, int(shape[1] * keep_fraction):int(shape[1] * (1 - keep_fraction))] = 0

    return spectral_data

@operation
@output_names("spectral_data")
@display_name("Absolute Square")
@categories(("General", "Mathematics"))
@visible('spectral_data', False)
@intent(ImageIntent, 'Absolute Square', output_map={'image': 'spectral_data'})
def absolute_square(spectral_data: np.ndarray) -> np.ndarray:
    return np.square(np.abs(spectral_data))

@operation
@output_names("data")
@display_name("Inverse Fourier Transform")
@categories(("General", "Mathematics"))
@visible('spectral_data', False)
@intent(ImageIntent, 'Inverse Fourier Transform', output_map={'image': 'data'})
def inverse_fourier_transform(spectral_data: np.ndarray) -> np.ndarray:
    return scipy.fft.ifft2(spectral_data)

@operation
@output_names("output")
@display_name("TupleGroupParameter Test")
@categories(("General", "Debug"))
def tuple_test(tuple_with_default: tuple = (1, 2, 3)) -> int:
    print("TupleGroupParameter Test")
    return 0


#!/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

def find_most_frequent_class(data_file):
    csv = np.genfromtxt(data_file, delimiter=',', skip_header=1)
    data, labels = csv[:, 1:], np.array(csv[:, 0], dtype=np.int64)

    return np.argmax(np.bincount(labels))

if __name__ == '__main__':
    most_frequent_class = find_most_frequent_class('data.csv')
    print("Class #", most_frequent_class)


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)


# Polynomial Regression

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Position_Salaries.csv')
X = dataset.iloc[:, 1:2].values
y = dataset.iloc[:, 2].values

# Splitting the dataset into the Training set and Test set
"""from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)"""

# Feature Scaling
"""from sklearn.preprocessing import StandardScaler
sc_X = StandardScaler()
X_train = sc_X.fit_transform(X_train)
X_test = sc_X.transform(X_test)"""

#fitting polynomial regressor to the dataset

#predict the new result with polynomial regression

y_pred=regressor.predict(6.5)

#visualizing the polynomial regression results

plt.scatter(X,y,color='red')
#plt.plot(X,lin_reg2.predict(X_poly),color='blue') or we can make it suitable for all other type
plt.plot(X,regressor.predict(poly_reg.fit_transform(X) ),color='blue')
plt.title("Truth or Bluff(for polynomial regression)")
plt.xlabel("position")
plt.ylabel("salary")
plt.show()

#visualizing the polynomial regression results for higher resolution

X_grid=np.arange(min(X),max(X),0.1)
X_grid=X_grid.reshape((len(X_grid),1))
plt.scatter(X,y,color='red')
#plt.plot(X,lin_reg2.predict(X_poly),color='blue') or we can make it suitable for all other type
plt.plot(X_grid,regressor.predict(poly_reg.fit_transform(X_grid) ),color='blue')
plt.title("Truth or Bluff(for polynomial regression)")
plt.xlabel("position")
plt.ylabel("salary")
plt.show()

Machine Learning A-Z Template Folder/Part 2 - Regression/Section 6 - Polynomial Regression/polynomial_regression_template.py


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, :]


## 1. Why Learn Calculus? ##

import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 3, 100)
y = -1*(x**2) + x*3 - 1
plt.plot(x,y)

## 4. Math Behind Slope ##

def slope(x1,x2,y1,y2):
    ## Finish the function here
    return 0
slope_one = slope(0,4,1,13)
slope_two = slope(5,-1,16,-2)
def slope(x1,x2,y1,y2):
    rc = (y2 - y1)/(x2 - x1)
    return rc
slope_one = slope(0,4,1,13)
slope_two = slope(5,-1,16,-2)

print(slope_one)
print(slope_two)

## 6. Secant Lines ##

import seaborn
seaborn.set(style='darkgrid')

def draw_secant(x_values):
    x = np.linspace(-20,30,100)
    y = -1*(x**2) + x*3 - 1
    plt.plot(x,y)
    # Add your code here.
    plt.show()
def draw_secant(x_values):
    x = np.linspace(-20,30,100)
    y = -1*(x**2) + x*3 - 1
    plt.plot(x,y)

    x_0 = x_values[0]
    x_1 = x_values[1]
    y_0 = -1*(x_0**2) + x_0*3 - 1
    y_1 = -1*(x_1**2) + x_1*3 - 1
    m = (y_1 - y_0) / (x_1 - x_0)
    b = y_1 - m*x_1

    y_secant = x*m + b
    plt.plot(x, y_secant, c='green')
    plt.show()
draw_secant([3,5])
draw_secant([3,10])
draw_secant([3,15])

Data Scientist In Python/Step 6 - Machine Learning Introduction/2. Calculus For Machine Learning/1. Understanding Linear and Nonlinear Functions.py


#!/usr/bin/env python3
import numpy as np
import sys

with open(sys.argv[1]) as input:
    lines = input.readlines()

lines = [list(line.strip()) for line in lines]

# Part 1
transposed = np.array(lines).T.tolist()

gamma_str = ''.join(['1' if col.count('1') > col.count('0') else '0' for col in transposed])
gamma = int(gamma_str, base = 2)

epsilon_str = ''.join(['0' if c == '1' else '1' for c in gamma_str])
epsilon = int(epsilon_str, base = 2)

print(gamma * epsilon)

# Part 2
def oxygen(lines, depth = 0):
    if len(lines) == 1:
        return int(''.join(lines[0]), base = 2)

    transposed = np.array(lines).T.tolist()

    most_common = '1' if transposed[depth].count('1') >= transposed[depth].count('0') else '0'

    return oxygen([line for line in lines if line[depth] == most_common], depth + 1)

def co2(lines, depth = 0):
    if len(lines) == 1:
        return int(''.join(lines[0]), base = 2)

    transposed = np.array(lines).T.tolist()

    most_common = '1' if transposed[depth].count('1') < transposed[depth].count('0') else '0'

    return co2([line for line in lines if line[depth] == most_common], depth + 1)

print(oxygen(lines) * co2(lines))


import numpy as np
from scipy.special import comb

# https://stats.stackexchange.com/a/157385
def rand_index_score(y, d):
  tp_fp = comb(np.bincount(y), 2).sum()
  tp_fn = comb(np.bincount(d), 2).sum()

  A = np.c_[(y, d)]

  tp = sum(comb(np.bincount(A[A[:, 0] == i, 1]), 2).sum() for i in set(y))

  fp = tp_fp - tp
  fn = tp_fn - tp

  tn = comb(len(A), 2) - tp - fp - fn

  return (tp + tn) / (tp + fp + fn + tn)

# https://www.kdnuggets.com/2020/06/centroid-initialization-k-means-clustering.html
def kmeans_plus_plus(x, k):
  c = [x[0]]

  for _ in range(1, k):
    dist = np.array([min([np.inner(c - xi, c - xi) for c in c]) for xi in x])
    probs = dist / dist.sum()
    cumulative_probs = probs.cumsum()
    r = np.random.rand()

    for j, p in enumerate(cumulative_probs):
      if r < p:
        i = j
        break

    c.append(x[i])

  return np.array(c)


from __future__ import annotations

import numpy as np

def modular_inverse(a: int, N: int) -> int:
    """Return the inverse of `a` modulo `N`.

    Args:
        a: The number to invert.
        N: The modulus. Must be > 1.

    Returns:
        The inverse of `a` modulo `N`.

    Raises:
        ValueError: if `N` <= 1 or the inverse does not exist.
    """
    if not N > 1:
        msg = f"modular_inverse: `N` ({N}) must be > 1."
        raise ValueError(msg)
    try:
        return pow(a, -1, N)
    except ValueError:
        msg = f"modular_inverse: invalid arguments a={a} and N={N}."
        raise ValueError(msg)

def continued_fraction(p: int, q: int) -> list[int]:
    """Given p/q, return its continued fraction.

    Args:
        p: The numerator.
        q: The denominator.

    Returns:
        The continued fraction expansion of p/q, [a0; a1, a2, ..., an].
    """
    coeffs = list()
    while q != 0:
        a = p // q
        coeffs.append(a)
        p, q = q, p - q * a
    return coeffs

def approximate_fraction(p: int, q: int, N: int) -> tuple[int, int]:
    """Given p/q, find the closest fraction a/b where b <= N.

    Args:
        p: The numerator.
        q: The denominator.

    Returns:
        The tuple (a, b).
    """
    # truncate sequence of continued fraction approximations when denominator >= N
    a1, a2 = 1, 0
    b1, b2 = 0, 1
    truncated = False
    for k in continued_fraction(p, q):
        if k * b1 + b2 >= N:
            truncated = True
            break
        a1, a2 = k * a1 + a2, a1
        b1, b2 = k * b1 + b2, b1

    if truncated:
        # use largest j where k/2 <= j < k and j*b1 + b2 < N.
        j = (N - b2) // b1
        if j >= k:
            pass
        elif k < 2 * j:  # found good j
            a1 = j * a1 + a2
            b1 = j * b1 + b2
        elif k == 2 * j:  # if k even, j = k/2 only admissible if the approximation is better
            next_a = j * a1 + a2
            next_b = j * b1 + b2
            if abs((p * 1.0) / q - (next_a * 1.0) / next_b) < abs((p * 1.0) / q - (a1 * 1.0) / b1):
                a1, b1 = next_a, next_b

    # else, no better approximation
    return a1, b1

def is_prime(n: int, num_iters: int = 40) -> bool:
    """Returns True if n is a probable prime. Implementation uses a
    Miller-Rabin primality test modified from
    https://gist.github.com/Ayrx/5884790.

    Args:
        n: The number to test.
        num_iters: The number of iterations to run.

    Returns:
        True if n is a probable prime.
    """

    if n == 2 or n == 3:
        return True

    if n % 2 == 0:
        return False

    r, s = 0, n - 1
    while s % 2 == 0:
        r += 1
        s //= 2
    for _ in range(num_iters):
        a = np.random.randint(2, n - 1)
        x = pow(a, s, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False
    return True