import numpy as np

#TODO
def imsharpen():
    """
    Doc string.
    """
    pass

def sobel_filter(type):
    assert type in ['ver', 'hor'], 'imagelib.sobel_filter: Invalid filter type'
    if type == 'ver':
        return np.array([[-1, 0, 1],
                         [-2, 0, 2],
                         [-1, 0, 1]])
    elif type == 'hor':
        return np.array([[1, 2, 1],
                         [0, 0, 0],
                         [-1, -2, -1]])

def laplassian_filter():
    filter = np.array([[ 0, -1,  0],
                       [-1,  4, -1],
                       [ 0, -1,  0]])
    return filter

def mean_blur_filter(filt_shape):
    return np.ones(filt_shape) / np.prod(filt_shape)

def imblur_mean(img, filt_shape):
    """
    Mean blur of an image.
    """
    filt = mean_blur_filter(filt_shape)
    return convolve(img, filt)

# TODO
def imblur_gaussian():
    pass

def convolve(img, filt):
    """
    img(numpy.ndarray): grayscale image
    filt(numpy.ndarray): 2D filter
    """
    assert len(img.shape) == 2, 'imagelib.convolve: Invalid image format'
    assert len(filt.shape) == 2, 'imagelib.convolve: Invalid filter format'
    assert np.prod(filt.shape) % 2 == 1, 'imagelib.convolve: Invalid filter format'

    img_h, img_w = img.shape
    filt_h, filt_w = filt.shape

    padding_w = (filt_w - 1) // 2
    padding_h = (filt_h - 1) // 2

    vertical_padding = np.zeros((img_h, padding_w))
    horizontal_padding = np.zeros((padding_h, img_w + 2 * padding_w))

    padded_img = np.concatenate([vertical_padding, img, vertical_padding], axis=-1)
    padded_img = np.concatenate([horizontal_padding, padded_img, horizontal_padding], axis=0)

    filtered_image = np.zeros(img.shape)

    for i in range(img_h):
        for j in range(img_w):
            # image patch of size [<padding_h>, <padding_w>] centered in [i, j].
            image_patch = padded_img[i : 2*padding_h+i+1, j : 2*padding_w+j+1]

            # apply filter to the patch
            filtered_image[i, j] = np.sum(filt * image_patch)

    return filtered_image

def sobel_edge_det(img, blur_filt_shape):
    img = imblur_mean(img, blur_filt_shape)

    filt_ver = sobel_filter('ver')
    edges_ver = convolve(img, filt_ver)

    filt_hor = sobel_filter('hor')
    edges_hor = convolve(img, filt_hor)

    edges_img = np.sqrt(edges_ver**2 + edges_hor**2)
    edges_img = edges_img / np.max(edges_img) * 255.
    edges_dir = np.arctan(edges_hor / edges_ver)

    return edges_img, edges_dir

def non_max_supression(edges, gradient):
    h, w = edges.shape

    Z = np.zeros(edges.shape)

    for i in range(1, h - 1):
        for j in range(1, w - 1):
            p, q = 255, 255

            if 3*np.pi/8 <= gradient[i][j] <= np.pi/2 or\
                -np.pi/2 <= gradient[i][j] < -3*np.pi/8:
                p = edges[i, j-1]
                q = edges[i, j+1]
            elif np.pi/8 <= gradient[i][j] < 3*np.pi/8:
                p = edges[i-1, j+1]
                q = edges[i+1, j-1]
            elif -np.pi/8 <= gradient[i][j] < np.pi/8:
                p = edges[i, j-1]
                q = edges[i, j+1]
            elif -3*np.pi/8 <= gradient[i][j] < -np.pi/8:
                p = edges[i-1, j-1]
                q = edges[i+1, j+1]

            if edges[i, j] >= p and edges[i, j] >= q:
                Z[i, j] = edges[i, j]

    return Z

def dual_threshold(edges, high, low):
    edges[edges < low] = 0.
    edges[edges >= high] = high

    threshold = np.zeros(edges.shape)
    threshold[edges > high] = 1.

    h, w = edges.shape

    def dfs(i, j):
        threshold[i][j] = 1.

        stack = []
        stack.append((i, j))

        while not len(stack) == 0:
            u, v = stack[-1]
            stack = stack[:-1]

            threshold[u][v] = 1.

            for x in range(-1, 2):
                for y in range(-1, 2):
                    newU = u + x
                    newV = v + y

                    if newU >= h or newU < 0 or newV >= w or newV < 0:
                        continue

                    if threshold[newU][newV] == 1:
                        continue

                    if low <= edges[newU][newV] <= high:
                        stack.append((newU, newV))

    for i in range(h):
        for j in range(w):
            if threshold[i, j] != 1 and edges[i, j] >= high:
                dfs(i, j)

    edges =  edges * threshold
    edges[edges > 0] = 255.

    return edges

def canny_edge(img, blur_filt_shape, high, low):
    """
    Canny edge detection algorithm implementation.
    """
    edges, edges_dir = sobel_edge_det(img, blur_filt_shape)
    edges = non_max_supression(edges, edges_dir)
    canny_edges = dual_threshold(edges, high, low)

    return canny_edges

# TODO
def histeq():
    """
    Histogram equalization.
    """
    pass

def hough_transform(edge, h, w, t):
    """
    Performs Hough transform.

    Args:
        edge(numpy.ndarray): edge[x, y] = 1 if pixel [x, y] contains an edge,
            otherwise it is 0.
        h(int): Number of bins with respect to the distance of the line from
            top left corner.
        w(int): Number of bins with respect to the angle of the normal from
            top left corner onto the line.
        t(int): Number of necessery votes in order to declare an existing line.
    Ret:
        List of tuples (rho, theta), one for every declared line.
    """

    voting_grid = [[0 for _ in range(w)] for _ in range(h)]

    d_theta = np.pi / w
    d_rho = np.hypot(edge.shape[0], edge.shape[1]) / h

    def vote(x, y):
        for i in range(w):
            theta = d_theta * i
            rho = np.cos(theta) * x + np.sin(theta) * y
            j = int(rho / d_rho)

            voting_grid[i][j] += 1

    for x in range(edge.shape[0]):
        for y in range(edge.shape[1]):
            if edge[x, y]:
                vote(x, y)

    lines = []

    for i in range(h):
        for j in range(w):
            if (voting_grid[i][j] >= t):
                lines.append((i * d_theta, d_rho * j))

    line_img = np.zeros(edge.shape, dtype=np.int32)
    for (theta, rho) in lines:
        for x in range(edge.shape[0]):
            y = int((rho - np.cos(theta) * x) / np.sin(theta))

            if 0 <= y < edge.shape[1]:
                line_img[x-1:x+1, y-1:y+1] = 1

        for y in range(edge.shape[1]):
            x = int((rho - np.sin(theta) * y) / np.cos(theta))

            if 0 <= x < edge.shape[0]:
                line_img[x-1:x+1, y-1:y+1] = 1

    return lines, line_img

#TODO:
def circle_hough_transform(edge, h, w, t):
    pass


import numpy as np

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

c = np.vstack((a,b))
print(c)
'''
[[1 2 3 4]
 [5 6 7 8]]
'''

d = np.hstack((a,b))
print(d)
'''
[1 2 3 4 5 6 7 8]
'''

a = np.array([1,2,3,4])[np.newaxis]
b = np.array([5,6,7,8])[np.newaxis]

print(a.T)
'''
[[1]
 [2]
 [3]
 [4]]
'''
c = np.hstack((a.T,b.T))
print(c)
'''
[[1 5]
 [2 6]
 [3 7]
 [4 8]]
 '''


import numpy as np
from numpy import sqrt

def mean(x: list) -> float:
    """given x can be an array or list output is the mean value"""
    mean_x = sum(x)/len(x)
    return mean_x

def mean_normalise(x: list) -> list:
    """Returns the mean normalized list for a given input list"""
    normalised_x = [xi - mean(x) for xi in x]
    return normalised_x

def std_dev(x: list) -> float:
    """Returns the standard deviation value for an input list"""
    norm_x = mean_normalise(x)
    var_x = sum(list(map(lambda x: x**2, norm_x)))
    return sqrt(var_x/len(x))

def mean_normalization(x: list) -> np.ndarray:
    """Returns the mean normalized array divided by the std.dev of the entries"""
    mean = np.mean(x, axis=0, keepdims=True)
    std = np.std(x, axis=0, keepdims=True)
    return (x - mean)/std

def r_sqr(y: np.ndarray, y_pred: np.ndarray) -> float:
    """Returns the R2 or the coefficient of determination
    for given Y and predicted Y arrays"""
    num = sum((y-y_pred)**2)
    den = sum((y-mean(y))**2)
    r_sqr = 1 - (num/den)
    return r_sqr[0]

def train_test_split(X: np.ndarray, Y: np.ndarray, test_split,shuffle=True):
    """Given the Independent (X) and Dependent (Y) variables,
    perform train-test split by random shuffling(default)"""
    try:
        dataset = np.hstack((X, Y.reshape(X.shape[0], 1)))  #create the dataset by concating X,Y
        shuffled_dataset = np.random.permutation(dataset)  #shuffle the dataset
        testset_length = np.int(len(shuffled_dataset)*test_split)  # length of test set
        trainset_length = len(shuffled_dataset) - testset_length   # length of training set
        X_train = shuffled_dataset[:trainset_length, :-1]
        Y_train = shuffled_dataset[:trainset_length, -1]
        X_test = shuffled_dataset[trainset_length:, :-1]
        Y_test = shuffled_dataset[trainset_length:, -1]

        return X_train, Y_train, X_test, Y_test

    except:
        print('Error !') # need to specify the kind and methods to change input
        print('Shape of Input X and Y arrays dont match !')

        return None


# independence_test.py
import numpy as np
from scipy.stats import norm

def independence_test(data):
    """
    Calculate independence test.
    This test tests the null hypothesis that two columns in data
    are independent or orthogonal to each other.

    Parameters
    ----------
    data : 2d array of floats

    Returns
    -------
    statistic : float
    p_value : float
        THe p-value for the two-sided test
    tau : float
    """

    n = len(data)

    # sort the array with X
    def Sort_Tuple(tup):
        return sorted(tup, key=lambda x: x[1])

    sorted_data = np.array(Sort_Tuple(data))
    X = sorted_data[:, 1]
    Y = sorted_data[:, 0]

    concordant = 0
    discordant = 0

    for i in range(n - 1):
        for j in range(i + 1, n):
            if Y[j] > X[i]:
                concordant += 1
            if Y[j] < Y[i]:
                discordant += 1

    tau = (concordant - discordant) / (concordant + discordant)

    z = 3 * tau * np.sqrt(n * (n - 1)) / np.sqrt(2 * (2 * n + 5))
    p_value = 2. * norm.sf(abs(z)) # two sided test

    return z, p_value, tau

def calculate_required_sample_size(data):
    # optimal sample size
    z_alpha = 1.96
    z_beta = 1.96
    _, _, tau = independence_test(data)
    min_n = 4 * (z_alpha + z_beta) ** 2 / (9 * tau ** 2)
    return int(min_n + 1)


#Homework. PLaying with matplotlib.

#Write a program that displays a plot of the functions
# f(x)=x, g(x)=x2 and h(x)=x3
#in the range [0, 4] on the one set of axes.

import numpy as np

import matplotlib.pyplot as plt

#Going to play with plotting first
#Trying example from the video

#l=np.arange(0.0, 10.0, 0.5)

#print(l)

#plt.plot(l , l**2)
#plt.show()

#Ok, now for the homework.

x=np.arange(0.0, 4.0)

print(x)

plt.plot(x, x)
plt.plot(x, x**2)
plt.plot(x, x**3)
plt.show()

#Essentially what I did was assume each function f(x), f(g), f(h) was
#its own plot. I then used the same program as demonstrated in the video
#but using the values given in the homework assignment.


"""In this example we are going to present several methods calculate the average value of certain measurements
which are corrupted with errors in measurements which introduce outlier values.

We are going to use two approaches:
1) replacing outliers with NaN (not-a-number) values and calculating the average value using nanmean() function.
2) using masked cubes which define which values in a cube shall be ignored when calculating the mean
"""

import numpy as np
from numcube import Index, Cube
import timeit

if __name__ == "__main__":
    # The cube will contain measurements taken each second during a period of 24 hours.
    hour_axis = Index("hour", range(1, 25))
    minute_axis = Index("minute", range(1, 61))
    second_axis = Index("second", range(1, 61))
    shape = (len(hour_axis), len(minute_axis), len(second_axis))

    # correct values which would ideally be measured
    values = np.random.rand(*shape)
    print("Theoretical sample (without measurement errors):")
    print("avg={0} sigma={1}".format(np.mean(values), np.std(values)))

    # Measurements of correct values corrupted with outliers, e.g. every 20th value will be multiplied by 100.
    corrupted_values = values + np.random.binomial(n=1, p=0.01, size=shape) * 100
    print("\nCorrupted sample (with measurement errors):")
    print("avg={0} sigma={1}".format(np.mean(corrupted_values), np.std(corrupted_values)))

    # create a Cube instance
    measurements = Cube(corrupted_values, [hour_axis, minute_axis, second_axis])

    # Now we have to decide how we are going to identify the outliers. There are several possible solutions.
    # For example, values less than 1st decile and greater than 9th decile are treated as outliers.
    lower = measurements.reduce(func=lambda a: np.percentile(a, 10.0))
    upper = measurements.reduce(func=lambda a: np.percentile(a, 90.0))

    # Alternatively we can calculate average value and standard deviation (sigma) with inclusion of all outliers,
    # and then exclude all values which are more distant from the average value than 3 sigmas.
    raw_avg = measurements.mean()
    raw_sigma = measurements.std()
    lower = raw_avg - 3 * raw_sigma
    upper = raw_avg + 3 * raw_sigma

    # Note: there are many more methods to identify outliers, however the goal of this example
    # is not to design the best method to exclude outliers. We will use the +/- 3 sigma method.

    # Replace outliers with NaNs. Use lambda to pass extra arguments to aggregation function.
    adjusted_measurements = measurements.apply(func=lambda x: x if lower <= x <= upper else np.nan)

    print("\nAfter exclusion of outliers (various methods):")

    # Now calculate the average with excluding the NaNs.
    avg = np.nanmean(adjusted_measurements.values)
    sigma = np.nanstd(adjusted_measurements.values)
    print("avg={0} sigma={1}".format(avg, sigma))

    # TODO: why it does not work?
    # avg_temperature = np.nanmean(temperature_adjusted)
    # avg = np.nanmean(adjusted_measurements)
    # sigma = np.nanstd(adjusted_measurements)
    # print("avg={0} sigma={1}".format(avg, sigma))

    avg = adjusted_measurements.nanmean()
    sigma = adjusted_measurements.nanstd()
    print("avg={0} sigma={1}".format(avg, sigma))

    # The following approach using reduce() function can be used for functions which
    # are defined in numpy package but they have not been added to Cube interface.
    avg = adjusted_measurements.reduce(np.nanmean)
    sigma = adjusted_measurements.reduce(np.nanstd)
    print("avg={0} sigma={1}".format(avg, sigma))

    # Here we are going to use much more powerful mechanism - masking cubes.
    # The following line masks all NaN values and they subsequently will be
    # ignored when calculating the mean value using normal mean() function.
    masked_measurements = adjusted_measurements.masked(np.isnan)
    avg = masked_measurements.mean()
    sigma = masked_measurements.std()
    print("avg={0} sigma={1}".format(avg, sigma))

    # However we can also use a function to define which values are going to be masked and which are not.
    # The following line will mask all values less than 1st decile and greater than 9th decile.
    # In other words, with this approach we can skip the replacement of outliers with NaNs.
    masked_measurements = measurements.masked(lambda c: c < lower or c > upper)
    avg = masked_measurements.mean()
    sigma = masked_measurements.std()
    print("avg={0} sigma={1}".format(avg, sigma))

    # The following line does exactly the same operation as the previous but it uses numpy vectorized expression,
    # therefore it is significantly faster than the previous when applied on large arrays.
    # Here we finally came to the potentially best solution to achieve what we asked for.
    masked_measurements = measurements.masked(lambda c: (c < lower) | (c > upper))  # Note the brackets!!!
    avg = masked_measurements.mean()
    sigma = masked_measurements.std()
    print("avg={0} sigma={1}".format(avg, sigma))

    # Here we are going to document the speed difference when masking with vectorized and non-vectorized
    # functions. As you can see, the vectorized operation is significantly (around 100x) faster.
    # Therefore use non-vectorized only when absolutely necessary!
    print("\nComparison of speed of masking operations:")
    repeat = 1
    setup = "from __main__ import measurements, lower, upper"
    func = "measurements.masked(lambda c: (c < lower) | (c > upper))"  # Note the brackets!!!
    timer = timeit.Timer(func, setup)
    t_vectorized = timer.timeit(repeat)
    print("vectorized mask: t={0} secs".format(t_vectorized))
    func = "measurements.masked(lambda c: c < lower or c > upper)"
    timer = timeit.Timer(func, setup)
    t_non_vectorized = timer.timeit(repeat)
    print("non-vectorized mask: t={0} secs".format(t_non_vectorized))
    print("speed gain={0}x".format(int(t_non_vectorized / t_vectorized)))


import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt

if __name__ == "__main__":

    # read data as 2D array of data type 'object'
    data = np.loadtxt('whData.dat', dtype=np.object, comments='#', delimiter=None)

    # read height data into 2D array (i.e. into a matrix)
    X = data[:, 1].astype(np.float)

    # Sample mean and std enough to parameterize a normal distribution
    mean = np.mean(X)
    std = np.std(X)

    # Plot the height data as circle markers
    plt.plot(X, np.zeros_like(X) + 0, 'o', color='b', alpha=0.5, label='data')

    # Plot the Gaussian fit for height data
    xmin, xmax = 140, 210
    x = np.linspace(xmin, xmax, 1000) # 1000 evenly spaced numbers over xmin and xmax for plotting normal distribution

    p = norm.pdf(x, mean, std) # specify a normal distribution for sample mean and std
    plt.plot(x, p, 'k', linewidth=2, color='y', label='normal') # Plot the gaussian

    plt.ylim(0,0.06)
    #specifying title and plotting title and legend
    title = "Univariate Gaussian Distribution fit to: mu = %.2f,  std = %.2f" % (mean, std)
    plt.title(title)
    plt.legend()

    #Saving the figure
    plt.savefig("gaussian_fit_to_height.pdf",  # facecolor='w', edgecolor='w',
                papertype=None, format='pdf', transparent=False,
                bbox_inches='tight', pad_inches=0.1)

    # Display figure
    plt.show()


import numpy as np

def binstep(y, th=0):
    return [1 if i > th else 0 for i in y]

def hebb_assoc_train(s, t):
    w = np.zeros((len(s[0]), len(t[0])))

    for r, row in enumerate(s):
        for c, col in enumerate(row):
            w[c] = [w[c, i] + col * t[r, i] for i in range(len(t[r]))]

    return w

def hebb_assoc_train_mat(s, t):
    p = [np.outer(np.reshape(s[i], (-1, 1)), t[i]) for i in range(len(s))]

    return np.sum(p, 0)

def hebb_assoc_test(x, w):
    y = [np.dot(x, w[:, i]) for i in range(len(w[0]))]

    return binstep(y)

def hebb_assoc_test_mat(x, w):
    return binstep(np.matmul(x, w))

if __name__ == '__main__':
    s = [[1, 0, 0, 0],
         [1, 1, 0, 0],
         [0, 0, 0, 1],
         [0, 0, 1, 1]]

    t = np.array([[1, 0],
                  [1, 0],
                  [0, 1],
                  [0, 1]])

    w = hebb_assoc_train_mat(s, t)
    y = hebb_assoc_test([1, 1, 0, 0], w)

    print(w)
    print(y)


from pyomo.environ import *
import time
#subproblem

def subproblem(y):
    model = ConcreteModel()
    model.dual = Suffix(direction=Suffix.IMPORT)

    model.mu = Var([1,2,3,4,5], within=NonNegativeReals)

    model.objective = Objective(expr=model.mu[1]*(4-10*y[1]-8*y[2])+model.mu[2]*(3-5*y[1]-8*y[3]), sense=maximize)

    model.constraint = Constraint(expr=-(-8+2*model.mu[1]+model.mu[2]-model.mu[3]) == 0)
    model.constraint2 = Constraint(expr=-(-6+model.mu[1]+model.mu[2]-model.mu[4]) == 0)
    model.constraint3 = Constraint(expr=-(2-model.mu[1]+model.mu[2]-model.mu[5]) == 0)

    solver_manager = SolverFactory('gurobi_persistent')
    solver_manager.set_instance(model)
    solver_manager.solve()
    mu = [model.mu[i].value for i in [1,2,3,4,5]]
    print(f"mu={mu}")

    z= [0]
    for c in [model.constraint,model.constraint2,model.constraint3]:
        z.append(model.dual[c])
    print(f"z={z}")
    return z,{i: model.mu[i].value for i in [1,2,3,4,5]}

def subproblem2(y):
    model = ConcreteModel()
    model.dual = Suffix(direction=Suffix.IMPORT)
    model.z= Var([1,2,3], within=NonNegativeReals)

    model.constraint1 = Constraint(expr=10*y[1]+8*y[2]-2*model.z[1]-model.z[2]+model.z[3]>=4)
    model.constraint2 = Constraint(expr=5*y[1]+8*y[3]-model.z[1]-model.z[2]-model.z[3]>=3)

    model.obj = Objective(expr=-8*model.z[1]-6*model.z[2]+2*model.z[3],sense=minimize)

    solver_manager = SolverFactory('gurobi_persistent')
    solver_manager.set_instance(model)
    solver_manager.solve()

    mu = [0]
    for c in [model.constraint1,model.constraint2]:
        mu.append(model.dual[c])

    z = [0]
    for i in [1,2,3]:
        z.append(model.z[i].value)

    return z,mu

def masterproblem():
    model = ConcreteModel()
    model.y = Var([1,2,3], within=Binary)
    model.theta = Var()
    model.constraint = Constraint(expr= model.y[1]+model.y[2]+model.y[3]>=1)
    model.cuts = ConstraintList()

    model.objective = Objective(expr=12*model.y[1]+20*model.y[2]+15*model.y[3]+model.theta,sense=minimize)
    UB = 1000000
    LB = -1000000
    y = [0,1,0,0]
    while(UB!=LB):
        z, mu = subproblem(y)
        print(f"mu={mu}")
        print(f"z={z}")
        model.cuts.add(expr=model.theta>=mu[1]*(4-10*model.y[1]-8*model.y[2])+mu[2]*(3-5*model.y[1]-8*model.y[3]))

        solver_manager = SolverFactory('glpk')
        results = solver_manager.solve(model)
        #print(model.display())

        UB = model.objective.value()
        print(f"UB={UB}")
        y = [0,model.y[1].value, model.y[2].value, model.y[3].value]
        print(f"y={y}")
        LB = upperBound(y, z)
        print(f"LB={LB}")

        print(f"theta={model.theta.value}")
        print("****")
        time.sleep(5)

def upperBound(y,z):
    return 12*y[1]+20*y[2]+15*y[3]-8*z[1]-6*z[2]+2*z[3]

masterproblem()


from scipy.stats import norm, linregress
from scipy import optimize
import matplotlib.mlab as mlab
import numpy as np

def teste(N):
    x=range(N)
    y=[]
    for i in x:
        y.append(rnd.normal())
    return x,y

def momentum(data,r):
    media=sum(data)/len(data)
    soma=0
    for i in data:
        soma+=(i-media)**r
    soma=soma/len(data)
    return soma

def normalize(data):
    mini=min(data)
    maxi=max(data)
    normdata=[]
    for i in data:
        normdata.append((i-mini)/(maxi-mini))
    return normdata

def variance(data):
    return momentum(data,2)

def skewness(data):
    n=len(data)
    return (n*(n-1))**(0.5)/(n-2)*(momentum(data,3)/momentum(data,2)**(3./2.))

def kurtosis(data):
    n=len(data)
    return (n-1)*(n+1)/((n-2)*(n-3))*(momentum(data,4))/(momentum(data,2)**2)-3*((n-1)**2)/((n-2)*(n-3))

def psd(data):
    """Calcula o PSD de uma série temporal."""
    # Define um intervalo para realizar o ajuste da reta
    INICIO = 10
    FIM = 200 if len(data)//2 > 200 else len(data)//2

    # O vetor com o tempo é o tamanho do número de pontos
    N = len(data)
    tempo = np.arange(len(data))
    # Define a frequência de amostragem
    dt = (tempo[-1] - tempo[0] / (N - 1))
    fs = 1 / dt
    # Calcula o PSD utilizando o MLAB
    power, freqs = mlab.psd(data, Fs = fs, NFFT = N, scale_by_freq = False)
    # Calcula a porcentagem de pontos utilizados na reta de ajuste
    # Seleciona os dados dentro do intervalo de seleção
    xdata = freqs[INICIO:FIM]
    ydata = power[INICIO:FIM]
    # Simula o erro
    yerr = 0.2 * ydata
    # Define uma função para calcular a Lei de Potência
    powerlaw = lambda x, amp, index: amp * (x**index)
    # Converte os dados para o formato LOG
    logx = np.log10(xdata)
    logy = np.log10(ydata)
    # Define a função para realizar o ajuste
    fitfunc = lambda p, x: p[0] + p[1] * x
    errfunc = lambda p, x, y, err: (y - fitfunc(p, x)) / err
    logyerr = yerr / ydata
    # Calcula a reta de ajuste
    pinit = [1.0, -1.0]
    out = optimize.leastsq(errfunc, pinit, args = (logx, logy, logyerr), full_output = 1)
    pfinal = out[0]
    index = pfinal[1]
    amp = 10.0 ** pfinal[0]
    # Retorna os valores obtidos
    return freqs, power, xdata, ydata, amp, index, powerlaw, INICIO, FIM

def dfa1d(timeSeries, grau):

	# 1. A série temporal {Xk} com k = 1, ..., N é integrada na chamada função perfil Y(k)
	x = np.mean(timeSeries)
	timeSeries = timeSeries - x
	yk = np.cumsum(timeSeries)
	tam = len(timeSeries)
	# 2. A série (ou perfil) Y(k) é dividida em N intervalos não sobrepostos de tamanho S
	sf = np.ceil(tam / 4).astype(np.int)
	boxratio = np.power(2.0, 1.0 / 8.0)
	vetoutput = np.zeros(shape = (1,2))
	s = 4
	while s <= sf:
		serie = yk
		if np.mod(tam, s) != 0:
			l = s * int(np.trunc(tam/s))
			serie = yk[0:l]
		t = np.arange(s, len(serie), s)
		v = np.array(np.array_split(serie, t))
		l = len(v)
		x = np.arange(1, s + 1)
		# 3. Calcula-se a variância para cada segmento v = 1,…, n_s:
		p = np.polynomial.polynomial.polyfit(x, v.T, grau)
		yfit = np.polynomial.polynomial.polyval(x, p)
		vetvar = np.var(v - yfit)
# 4. Calcula-se a função de flutuação DFA como a média das variâncias de cada intervalo
		fs = np.sqrt(np.mean(vetvar))
		vetoutput = np.vstack((vetoutput,[s, fs]))
		# A escala S cresce numa série geométrica
		s = np.ceil(s * boxratio).astype(np.int)
	# Array com o log da escala S e o log da função de flutuação
	vetoutput = np.log10(vetoutput[1::1,:])
	# Separa as colunas do vetor 'vetoutput'
	x = vetoutput[:,0]
	y = vetoutput[:,1]
	# Regressão linear
	slope, intercept, _, _, _ = linregress(x, y)
	# Calcula a reta de inclinação
	predict_y = intercept + slope * x
	# Calcula o erro
	# Retorna o valor do ALFA, o vetor 'vetoutput', os vetores X e Y,
	# o vetor com os valores da reta de inclinação e o vetor de erros
	return slope, x, y, predict_y

if __name__ == "__main__":
    import matplotlib.pyplot as plt
    import numpy.random as rnd
    x,y=teste(8192)
    plt.figure(figsize=(20, 12))
    #Plot da série temporal
    ax1 = plt.subplot(211)
    ax1.set_title("Gaussian RNG", fontsize=18)
    (mu,sigma)=norm.fit(y)
    n, bins, patches = ax1.hist(y, 60, density=1, facecolor='powderblue', alpha=0.75)
    ax1.plot(bins,norm.pdf(bins,mu,sigma), c="black", linestyle='--')
    #Plot e cálculo do DFA
    ax2 = plt.subplot(223)
    slope,xdfa,ydfa,predict_y=dfa1d(y, 1)
    ax2.set_title(r"Detrended Fluctuation Analysis -DFA $\alpha$={0:.3}".format(slope, fontsize=15))
    ax2.plot(xdfa,ydfa, marker='o', linestyle='', color="#12355B")
    ax2.plot(xdfa, predict_y, color="#9DACB2")
    #Plot e cálculo do PSD
    freqs, power, xdata, ydata, amp, index, powerlaw, INICIO, FIM = psd(y)
    ax3 = plt.subplot(224)
    ax3.set_title(r"Power Spectrum Density - PSD $\beta$={0:.3}".format(index, fontsize=15))
    ax3.set_yscale('log')
    ax3.set_xscale('log')
    ax3.plot(freqs, power, '-', color = 'deepskyblue', alpha = 0.7)
    ax3.plot(xdata, ydata, color = "darkblue", alpha = 0.8)
    ax3.axvline(freqs[INICIO], color = "darkblue", linestyle = '--')
    ax3.axvline(freqs[FIM], color = "darkblue", linestyle = '--')
    ax3.plot(xdata, powerlaw(xdata, amp, index),color="#D65108", linestyle='-', linewidth = 3, label = '$%.4f$' %(index))
    ax2.set_xlabel("log(s)")
    ax2.set_ylabel("log F(s)")
    ax3.set_xlabel("Frequência (Hz)")
    ax3.set_ylabel("Potência")
    plt.show()


# -*- coding: utf-8 -*-#
'''
# Name:         DrawActivators2
# Description:
# Author:       super
# Date:         2020/5/24
'''

import numpy as np
import matplotlib.pyplot as plt

from Activators.Relu import *
from Activators.Elu import *
from Activators.LeakyRelu import *
from Activators.Sigmoid import *
from Activators.Softplus import *
from Activators.Step import *
from Activators.Tanh import *
from Activators.BenIdentity import *

def Draw(start,end,func,lable1,lable2):
    z = np.linspace(start, end, 200)
    a = func.forward(z)
    da, dz = func.backward(z, a, 1)

    p1, = plt.plot(z,a)
    p2, = plt.plot(z,da)
    plt.legend([p1,p2], [lable1, lable2])
    plt.grid()
    plt.xlabel("input : z")
    plt.ylabel("output : a")
    plt.title(lable1)
    plt.show()

if __name__ == '__main__':
    Draw(-5,5,CRelu(),"Relu Function","Derivative of Relu")
    Draw(-4,4,CElu(0.8),"ELU Function","Derivative of ELU")
    Draw(-5,5,CLeakyRelu(0.01),"Leaky Relu Function","Derivative of Leaky Relu")
    Draw(-5,5,CSoftplus(),"Softplus Function","Derivative of Softplus")
    Draw(-7,7,CBenIdentity(),"BenIdentity Function","Derivative of BenIdentity")


import numpy as np
import sympy
from sympy import Matrix
sympy.var('xi, le, E, cosr, sinr, rho, A1, A2, Izz1, Izz2')

A = A1 + (A2 - A1)*(xi - (-1))/(1 - (-1))
Izz = Izz1 + (Izz2 - Izz1)*(xi - (-1))/(1 - (-1))
Nu1 = (1-xi)/2
Nu2 = (1+xi)/2

R = Matrix([[  cosr,  sinr , 0, 0, 0, 0],
            [ -sinr,  cosr , 0, 0, 0, 0],
            [ 0,  0 , 1, 0, 0, 0],
            [ 0, 0, 0,  cosr,  sinr , 0],
            [ 0, 0, 0, -sinr,  cosr , 0],
            [ 0, 0, 0, 0,  0 , 1]])

ONE = sympy.Integer(1)
for leg_poly in [True]:
    if leg_poly:
        Nv1 = ONE/2 - 3*xi/4 + 1*xi**3/4
        Nv2 = le*(ONE/8 - 1*xi/8 - 1*xi**2/8 + 1*xi**3/8)
        Nv3 = ONE/2 + 3*xi/4 - 1*xi**3/4
        Nv4 = le*(-ONE/8 - 1*xi/8 + 1*xi**2/8 + 1*xi**3/8)
    else: # Hermitian cubic functions
        Nv1 = ONE/4*(1-xi)**2*(2+xi)
        Nv2 = le*1/8*(1-xi)**2*(1+xi)
        Nv3 = ONE/4*(1+xi)**2*(2-xi)
        Nv4 = le*1/8*(1+xi)**2*(xi-1)
    Nu = Matrix([[Nu1, 0,  0, Nu2,  0,  0]])
    Nv = Matrix([[0, Nv1, Nv2,  0, Nv3, Nv4]])
    Nbeta = -(2/le)*sympy.diff(Nv, xi)

    Nuxi = sympy.diff(Nu, xi)
    Nbetaxi = Nbeta.diff(xi)

    Ke = sympy.zeros(6, 6)
    Me = sympy.zeros(6, 6)

    Ke[:, :] = (2/le)*E*Izz*Nbetaxi.T*Nbetaxi + (2/le)*E*A*Nuxi.T*Nuxi
    Me[:, :] = (le/2)*rho*(A*Nu.T*Nu + A*Nv.T*Nv + Izz*Nbeta.T*Nbeta)

    #NOTE procedure to compute lumped matrix when cross section changes
    x = (xi + 1)*le/2
    mA = sympy.integrate((le/2)*rho*A, (xi, -1, 0))
    mB = sympy.integrate((le/2)*rho*A, (xi, 0, +1))
    IzzA = sympy.integrate((le/2)*(Izz + x**2*rho*A), (xi, -1, 0))
    IzzB = sympy.integrate((le/2)*(Izz + (le-x)**2*rho*A), (xi, 0, +1))
    Me_lumped = Matrix([[mA, 0, 0, 0, 0, 0],
                        [0, mA, 0, 0, 0, 0],
                        [0, 0, IzzA, 0, 0, 0],
                        [0, 0, 0, mB, 0, 0],
                        [0, 0, 0, 0, mB, 0],
                        [0, 0, 0, 0, 0, IzzB]])

    # integrating matrices in natural coordinates

    for ind, val in np.ndenumerate(Ke):
        Ke[ind] = sympy.simplify(sympy.integrate(val, (xi, -1, 1)))

    for ind, val in np.ndenumerate(Me):
        Me[ind] = sympy.simplify(sympy.integrate(val, (xi, -1, 1)))

    Me_lumped = sympy.simplify(Me_lumped)

    print('printing Me')
    print(Me)
    print('printing Me_lumped')
    print(Me_lumped)

    K = sympy.simplify(R.T*Ke*R)

    if leg_poly:
        print('K integrated with Legendre polynomial')
    else:
        print('K integrated with Hermitian cubic polynomial')

    print('printing K for code')
    for ind, val in np.ndenumerate(K):
        i, j = ind
        si = 'c1' if i < 3 else 'c2'
        sj = 'c1' if j < 3 else 'c2'
        print('        K[%d+%s, %d+%s]' % (i%3, si, j%3, sj), '+=', K[ind])

    if leg_poly:
        print('M integrated with Legendre polynomial')
    else:
        print('M integrated with Hermitian polynomial')

    M = sympy.simplify(R.T*Me*R)
    M_lumped = sympy.simplify(R.T*Me_lumped*R)

    print('printing M for code')
    for ind, val in np.ndenumerate(M):
        if val == 0:
            continue
        i, j = ind
        si = 'c1' if i < 3 else 'c2'
        sj = 'c1' if j < 3 else 'c2'
        print('        M[%d+%s, %d+%s]' % (i%3, si, j%3, sj), '+=', M[ind])

    print('printing M_lumped for code')
    for ind, val in np.ndenumerate(M_lumped):
        if val == 0:
            continue
        i, j = ind
        si = 'c1' if i < 3 else 'c2'
        sj = 'c1' if j < 3 else 'c2'
        print('        M[%d+%s, %d+%s]' % (i%3, si, j%3, sj), '+=', M_lumped[ind])


import numpy
from scipy import optimize
import pylab

# based on http://lister.dulci.duhs.duke.edu/~cliburn/summer-school/python/_build/html/model_fitting.html

def resid(avec, x, y):
    """ the residual function -- this is what will be minimized by the
        scipy.optimize.leastsq() routine.  avec is the parameters we
        are optimizing -- they are packed in here, so we unpack to
        begin.  (x, y) are the data points

        scipy.optimize.leastsq() minimizes:

           x = arg min(sum(func(y)**2,axis=0))
                    y

        so this should just be the distance from a point to the curve,
        and it will square it and sum over the points
        """

    a0, a1 = avec

    # note: if we wanted to deal with error bars, we would weight each
    # residual accordingly
    return y - a0*numpy.exp(a1*x)

# make up some experimental data
a0 = 2.5
a1 = 2./3.
sigma = 2.0

x = numpy.linspace(0.0, 4.0, 25)
y = a0*numpy.exp(a1*x) + sigma*numpy.random.randn(len(x))

pylab.scatter(x,y)
pylab.errorbar(x, y, yerr=sigma, fmt=None, label="_nolegend_")

# initial guesses
a0 = 1
a1 = 1

# fit -- here the args is a tuple of objects that will be added to the
# argument lists for the function to be minimized (resid in our case)
afit, flag = optimize.leastsq(resid, [a0, a1], args=(x, y))

print flag
print afit

p = pylab.plot(x, afit[0]*numpy.exp(afit[1]*x),
           label=r"$a_0 = $ %f; $a_1 = $ %f" % (afit[0], afit[1]))

pylab.legend(numpoints=1, frameon=False)

pylab.savefig("nonlinear-scipy.png")


import jax
import jax.numpy as jnp

def make_mog():
    from jax.scipy.stats import multivariate_normal as mvn
    mus = jnp.array([[0.0, 0.0],
                    [2.5,  4.0],
                    [4.0,  0.0]])

    sigmas = jnp.array([ [[1.0, 0.0],
                         [0.0, 2.0]],

                        [[2.0, -1.0],
                         [-1.0, 1.0]],

                        [[1.0, 0.1],
                         [0.1, 2.0]] ])

    # @jax.jit
    def logprob(z):
        return jnp.log(mvn.pdf(z, mean=mus[0], cov=sigmas[0]) + \
                      mvn.pdf(z, mean=mus[1], cov=sigmas[1]) + \
                      mvn.pdf(z, mean=mus[2], cov=sigmas[2]))

    return logprob

def make_egg_carton_logprob():
    pass

""" adapted from https://github.com/VincentStimper/normalizing-flows/blob/master/normflow/distributions.py"""

_norm = lambda x: jnp.sqrt(jnp.sum(jnp.square(x)))

def make_two_moons():

    def logprob(z):
        """
        log(p) = - 1/2 * ((norm(z) - 2) / 0.2) ** 2
                 + log(  exp(-1/2 * ((z[0] - 2) / 0.3) ** 2)
                       + exp(-1/2 * ((z[0] + 2) / 0.3) ** 2))
        :param z: value or batch of latent variable
        :return: log probability of the distribution for z
        """
        logp = - 0.5 * ((_norm(z) - 2) / 0.2) ** 2 \
                + jnp.log(jnp.exp(-1/2 * ((z[0] - 2) / 0.3) ** 2)
                          + jnp.exp(-1/2 * ((z[0] + 2) / 0.3) ** 2))
        return logp

    return logprob

def make_circular_mog(n_modes=8):
    pi = 3.141592658
    scale = 2 / 3 * jnp.sin(pi / n_modes)

    def logprob(z):
        components = []
        for i in range(n_modes):
            c = ((z[0] - 2 * jnp.sin(2 * pi / n_modes * i)) ** 2
                  + (z[1] - 2 * jnp.cos(2 * pi / n_modes * i)) ** 2)\
                 / (2 * scale ** 2)
            components.append(c)

        log_p = - jnp.log(2 * pi * scale ** 2 * n_modes) \
                + jax.scipy.special.logsumexp(-jnp.array(components))
        return log_p

    return logprob

def make_ring_mixture(n_rings=2):
    n_rings = n_rings
    scale = 1 / 4 / n_rings

    def logprob(z):
        components = []
        for i in range(n_rings):
            c = ((_norm(z) - 2 / n_rings * (i + 1)) ** 2) \
                 / (2 * scale ** 2)
            components.append(c)
        return jax.scipy.special.logsumexp(-jnp.array(components))

    return logprob

def test():
    # logprob = make_mog()
    # needs_extra_vmap = False

    needs_extra_vmap = True
    # logprob = make_two_moons()
    # logprob = make_circular_mog()
    logprob = make_ring_mixture()

    import numpy as np
    from matplotlib import pyplot as plt
    # pts = np.linspace(-7, 7, 1000)
    pts = np.linspace(-4, 4, 1000)
    # pts = np.linspace(-7, 7, 10)
    X, Y = np.meshgrid(pts, pts)
    pos = np.empty(X.shape + (2,))
    pos[:, :, 0] = X
    pos[:, :, 1] = Y

    if needs_extra_vmap:
        Z = np.exp(jax.vmap(jax.vmap(logprob))(pos))
    else:
        Z = np.exp(jax.vmap(logprob)(pos))

    f, ax = plt.subplots()
    ax.contour(X, Y, Z,)
    # ax.set_xlim(-2, 6)
    # ax.set_ylim(-5, 7)
    plt.show()

if __name__ == '__main__':
    test()


import numpy as np
import random
import binascii

def getCodewords(inputchar):
    # Generator Matrix
    G = np.array([[1,0,0,0,1,1,1],
        [0,1,0,0,1,0,1],
        [0,0,1,0,1,1,0],
        [0,0,0,1,0,1,1]])

    # Message
    binary_m = format(ord(inputchar), 'b')
    if len(binary_m) < 8:
        pad = '0'
        binary_m = (8 - len(binary_m)) * pad + binary_m
    m = np.array([[0,0,0,0,0,0,0,0]])
    print('\n{}'.format(binary_m))
    for i in range(0,8):
        m[0][i] = binary_m[i]

    M = np.array([[0,0,0,0],[0,0,0,0]])
    M[0] = np.array(m[0][0:4])
    M[1] = np.array(m[0][4:8])
    # Codeword
    C1 = np.dot(M[0],G) % 2
    C2 = np.dot(M[1],G) % 2
    codes = [C1, C2]
    print('Original Codewords: {}'.format(codes))

    return codes

def noise(codeword, probability):
    R = codeword
    # Random Error Depending on probability
    error = []
    errorcount = 0
    for x in range(0,6):
        if random.random() < probability:
            error = error + [x]
            R[x] = (R[x] + 1) % 2
            errorcount += 1
    print('Noisy Message: {} Error: Positions {}'.format(R, error))
    return [R, error]

def decode(received):
    R = received
    H = np.array([[1,1,1,0,1,0,0],
        [1,0,1,1,0,1,0],
        [1,1,0,1,0,0,1]])

    Ht = np.transpose(H)

    ## Syndromes Calculated with Venn Diagram Equations
    S1 = R[0] + R[1] + R[2] + R[4]
    S2 = R[0] + R[2] + R[3] + R[5]
    S3 = R[0] + R[1] + R[3] + R[6]
    S1 = S1 % 2
    S2 = S2 % 2
    S3 = S3 % 2
    S = [[S1, S2, S3]]

    ## Syndrome calculated with rHt
    Sv = np.dot(R, Ht) % 2
    # if Sv != [0,0,0]:
    #     print('Error Detected')
    elocation = -1
    ## Find matching column index in parity matrix
    for i in range(0,7):
        if np.all(Ht[i][:]==Sv): # rHt syndrome
            print('Error detected in position {}'.format(i))
            elocation = i
        else:
            continue
    # for i in range(0,6):
    #     if np.all(Ht[i][:]==S[0]): # Equations syndrome
    #         print(Ht[i], i)
    #     else:
    #         continue

    ## correcting error
    if elocation == -1:
        print('NO ERROR CORRECTED')
        R[elocation] = (R[elocation]) % 2
        corrected=0
    else:
        print('ERROR CORRECTED')
        R[elocation] = (R[elocation] + 1) % 2
        corrected=1
    print('Codeword: {}'.format(R))

    decoded = R[:4] # message is first four bits

    return [decoded, corrected]

def translate(decodemsgs):
    binstr = ''.join(str(x) for x in decodemsgs[0])
    binstr = binstr + ''.join(str(x) for x in decodemsgs[1])
    translatedcode = chr(int(binstr,2))

    return translatedcode

def hamming(message,probability):
    print('Encoding and Decoding "{}"'.format(message))
    decodemessage = ''
    errorLocs = []
    correctedlets = []
    for letter in message:
        A = getCodewords(letter)
        code1 = noise(A[0], probability)
        code2 = noise(A[1], probability)
        if decode(code1[0])[1] and decode(code2[0])[1]:
            correctedlets += [letter]
        result = translate([decode(code1[0])[0], decode(code2[0])[0]])
        errors = [letter + ':{} {}'.format(code1[1],code2[1])]
        errorLocs = errorLocs + errors
        decodemessage += result
    print('\nYour message was {0}'.format(decodemessage))
    print(errorLocs)
    return [decodemessage, errorLocs, correctedlets]

hamming('Hello',.1)


# offset_piston_motion.py
"""
Offset Piston Motion Animation using Matplotlib
Author: Peter D. Kazarinoff, 2021
MIT License
"""

# import the necessary packages
import numpy as np
from numpy import pi, sin, cos, sqrt
import matplotlib.pyplot as plt
import matplotlib.animation as animation

# input parameters
r = 1.0  # crank radius
l = 4.0  # connecting rod length
d = 0.5  # offset distance
rot_num = 6 # number of crank rotations
increment = 0.1 # angle incremement

# create the angle array, where the last angle is the number of rotations*2*pi
angle_minus_last = np.arange(0,rot_num*2*pi,increment)
angles = np.append(angle_minus_last, rot_num*2*pi)

X1=np.zeros(len(angles)) # crank x-positions: Point 1
Y1=np.zeros(len(angles)) # crank y-positions: Point 1
X2=np.zeros(len(angles)) # connecting rod x-positions: Point 2
Y2=np.zeros(len(angles)) # connecting rod y-positions: Point 2

# calculate the crank and connecting rod positions for each angle
for index,theta in enumerate(angles, start=0):
    x1 = r*cos(theta) # x-cooridnate of the crank: Point 1
    y1 = r*sin(theta) # y-cooridnate of the crank: Point 1
    x2 = d # x-coordinate of the rod: Point 2
    y2 = r*sin(theta) + sqrt(l**2 - (r*cos(theta)-d)**2) # y-coordinate of the rod: Point 2
    X1[index]=x1 # crankshaft x-position
    Y1[index]=y1 # crankshaft y-position
    X2[index]=x2 # connecting rod x-position
    Y2[index]=y2 # connecting rod y-position

# set up the figure and subplot
fig = plt.figure()
fig.canvas.set_window_title('Matplotlib Animation')
ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, xlim=(-4,4), ylim=(-2,6))
ax.grid()
ax.set_title('Offset Piston Motion Animation')
ax.axes.xaxis.set_ticklabels([])
ax.axes.yaxis.set_ticklabels([])
line, = ax.plot([], [], 'o-', lw=5, color='#de2d26')

# initialization function
def init():
    line.set_data([], [])
    return line,

# animation function
def animate(i):
    x_points = [0, X1[i], X2[i]]
    y_points = [0, Y1[i], Y2[i]]
    line.set_data(x_points, y_points)
    return line,

# call the animation
ani = animation.FuncAnimation(fig, animate, init_func=init, frames=len(X1), interval=40, blit=True, repeat=False)
## to save animation, uncomment the line below:
## ani.save('offset_piston_motion_animation.mp4', fps=30, extra_args=['-vcodec', 'libx264'])

# show the animation
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Sun Dec 10 20:28:24 2017

@author: tzave
"""

import numpy as np
import matplotlib.pyplot as plt

def FixedPoint(x0, g, tol, N=None):
    i = 1
    p0 = x0
    while (True if N is None else (i < N)):
        p = g(p0)
        if np.abs(p-p0) < tol:
            break
        i = i + 1
        p0 = p
    return {'p': p, 'iterations': i}

def f(x):
    return np.power(x, 3) + -x - 1

def g1(x):
    return np.power(x + 1, 1/3)

def dg1(x):
    return np.power(x + 1, -2/3) / 3

x0 = 1
result = FixedPoint(x0, g1, 1e-5)
root = result['p']
iterations = result['iterations']
print('Root found after ', iterations, ' iterations')

x = np.arange(1, 2, 0.1)
plt.close('all')
plt.figure(1)
plt.plot(x, f(x), root, f(root), '*r')
plt.grid()

x = np.arange(1, 2, 0.01)
plt.figure(2)
plt.plot(x, dg1(x), label='dg1')
plt.plot(x, np.ones(x.shape), label='y = 1')
plt.plot(x, -np.ones(x.shape), label='y = -1')
plt.ylim(-1.5, 1.5)
plt.legend()
plt.grid()


from abc import ABC, abstractmethod
import numpy as np

class compounding(ABC):
    @abstractmethod
    def getRate(self, discountFactor, yearFrac):
        pass

    @abstractmethod
    def getDF(self, rate, yearFrac):
        pass

class continuouslyCompounded(compounding):
    def getRate(self, discountFactor, yearFrac):
        return -np.log(discountFactor) / yearFrac

    def getDF(self, rate, yearFrac):
        return np.exp(-rate * yearFrac)

class simplyCompounded(compounding):
    def getRate(self, discountFactor, yearFrac):
        return (1 - discountFactor) / (yearFrac * discountFactor)

    def getDF(self, rate, yearFrac):
        return 1 / (1 + rate * yearFrac)

class annually_k_Spot(compounding):
    def __init__(self, k=1):
        self.k = k

    def getRate(self, discountFactor, yearFrac):
        return self.k / discountFactor ** (1 / (self.k * yearFrac)) - self.k

    def getDF(self, rate, yearFrac):
        return (1 + rate / self.k) ** (-self.k * yearFrac)


# %%
import numpy as np
import matplotlib.pyplot as plt
from copy import deepcopy
from sklearn import datasets
# from sklearn.base import clone
from sklearn.linear_model import (LinearRegression, LogisticRegression, SGDRegressor, Ridge, Lasso, ElasticNet)
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler

# %%
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)

# %%
plt.figure(figsize=(10, 6))
plt.scatter(X, y)
plt.show()

# %%
X_b = np.c_[np.ones((100, 1)), X]  # Add X[i, 0] = 1 for each i
theta_best = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)
print(f"Best parameters: intercept = {theta_best[0, 0]:.2f}, coef = {theta_best[1, 0]:.2f}")

# %%
np.set_printoptions(precision=1, floatmode="fixed")

# %%
X_new = np.array([[0], [2]])
X_new_b = np.c_[np.ones((2, 1)), X_new]
y_predict = X_new_b.dot(theta_best)
print(f"Precise values for {X_new[:, 0]}: {3.0 * X_new[:, 0] + 4.0}")
print(f"Predictions for    {X_new[:, 0]}: {y_predict[:, 0]}")

# %%
np.set_printoptions(precision=None, floatmode=None)

# %%
plt.plot(X_new, y_predict, "r-")
plt.plot(X, y, "b.")
plt.axis([0, 2, 0, 15])
plt.show()

# %%
lin_reg = LinearRegression()
lin_reg.fit(X, y)
print(f"Best parameters:   intercept = {theta_best[0, 0]:.2f}, coef = {theta_best[1, 0]:.2f}")
print(f"Linear regression: intercept = {lin_reg.intercept_[0]:.2f}, coef = {lin_reg.coef_[0, 0]:.2f}")

# %%
print(f"Precise values for   {X_new[:, 0]}: {3.0 * X_new[:, 0] + 4.0}")
print(f"Predictions (LR) for {X_new[:, 0]}: {y_predict[:, 0]}")

# %%
theta_best_svd, residuals, rank, s = np.linalg.lstsq(X_b, y, rcond=1e-6)
print(f"Parameters using lstsq(): intercept = {theta_best_svd[0, 0]:.2f}, coef = {theta_best_svd[1, 0]:.2f}")

# %%
theta_best_pinv = np.linalg.pinv(X_b).dot(y)
print(f"Parameters using pinv(): intercept = {theta_best_pinv[0, 0]:.2f}, coef = {theta_best_pinv[1, 0]:.2f}")

# %%
eta = 0.1  # learning rate
n_iterations = 1_000
m = 100
theta = np.random.randn(2, 1)  # random initialization

# %%
for iteration in range(n_iterations):
    gradients = 2 / m * X_b.T.dot(X_b.dot(theta) - y)
    theta = theta - eta * gradients

# %%
print(f"Parameters using gradient descent: intercept = {theta[0, 0]:.2f}, coef = {theta[1, 0]:.2f}")

# %%
n_epochs = 50
t0, t1 = 5, 50

# %%
def learning_schedule(t):
    return t0 / (t + t1)

# %%
theta = np.random.randn(2, 1)

# %%
for epoch in range(n_epochs):
    for i in range(m):
        random_index = np.random.randint(m)
        xi = X_b[random_index:random_index + 1]
        yi = y[random_index:random_index + 1]
        gradients = 2 * xi.T.dot(xi.dot(theta) - yi)
        eta = learning_schedule(epoch * m + i)
        theta = theta - eta * gradients

# %%
print(f"Parameters using stochastic gradient descent: intercept = {theta[0, 0]:.2f}, coef = {theta[1, 0]:.2f}")

# %%
print("y.shape =", y.shape, "y.ravel().shape =", y.ravel().shape)

# %%
sgd_reg = SGDRegressor(max_iter=1000, tol=1e-3, penalty=None, eta0=0.1)
sgd_reg.fit(X, y.ravel())

# %%
print(f"Parameters for sgd_reg: intercept = {sgd_reg.intercept_[0]:.2f}, coef = {sgd_reg.coef_[0]:.2f}")

# %%
m = 100
X = 6 * np.random.rand(m, 1) - 3
y = 0.5 * X ** 2 + X + 2 + np.random.randn(m, 1)

# %%
plt.plot(X, y, "b.")
plt.show()

# %%
poly_features = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly_features.fit_transform(X)
print(f"X[0]: {X[0]}, X_poly[0]: {X_poly[0]}")

# %%
lin_reg = LinearRegression()
lin_reg.fit(X_poly, y)
print("intercept:", lin_reg.intercept_, "coef:", lin_reg.coef_)

# %%
plt.plot(*zip(*sorted(zip(X, lin_reg.predict(X_poly)))), "r-")
plt.plot(X, y, "b.")
plt.show()

# %%
# noinspection PyShadowingNames
def compute_learning_curves(model, X, y, extra_epochs=10):
    X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)
    train_errors, val_errors = [], []

    # noinspection PyShadowingNames
    def compute_learning_curve_value(m, model):
        # if "coef_" in dir(model) and "intercept_" in dir(model):
        #     print(m, model.coef_, model.intercept_)
        model.fit(X_train[:m], y_train[:m])
        y_train_predict = model.predict(X_train[:m])
        y_val_predict = model.predict(X_val)
        train_errors.append(mean_squared_error(y_train[:m], y_train_predict))
        val_errors.append(mean_squared_error(y_val, y_val_predict))

    for m in range(1, len(X_train)):
        # compute_learning_curve_value(m, clone(model))
        compute_learning_curve_value(m, model)
    for m in range(extra_epochs):
        compute_learning_curve_value(len(X_train), model)

    return train_errors, val_errors

# %%
# noinspection PyShadowingNames
def plot_learning_curves(model, X, y, max_cap=3, extra_epochs=10):
    train_errors, val_errors = compute_learning_curves(model, X, y, extra_epochs=extra_epochs)
    plt.plot(np.clip(np.sqrt(train_errors), 0, max_cap), "r-+", linewidth=2, label="train")
    plt.plot(np.clip(np.sqrt(val_errors), 0, max_cap), "b-", linewidth=3, label="val")
    plt.show()

# %%
# noinspection PyShadowingNames
def plot_avg_learning_curves(model, epochs, X, y, max_cap=3, extra_epochs=10):
    all_train_errors, all_val_errors = [], []
    for e in range(epochs):
        train_errors, val_errors = compute_learning_curves(model, X, y, extra_epochs=extra_epochs)
        all_train_errors.append(train_errors)
        all_val_errors.append(val_errors)
    avg_train_errors = np.average(all_train_errors, axis=0)
    avg_val_errors = np.average(all_val_errors, axis=0)
    plt.plot(np.clip(np.sqrt(avg_train_errors), 0, max_cap), "r-+", linewidth=2, label="train")
    plt.plot(np.clip(np.sqrt(avg_val_errors), 0, max_cap), "b-", linewidth=3, label="val")
    plt.show()

# %%
lin_reg = LinearRegression(n_jobs=64)
plot_learning_curves(lin_reg, X, y)

# %%
plot_avg_learning_curves(lin_reg, 25, X, y)

# %%
polynomial_regression = Pipeline([
    ("poly_features", PolynomialFeatures(degree=12, include_bias=False)),
    ("lin_reg", LinearRegression(n_jobs=32)),
])
# Increase the number of epochs to something like 200 to get more consistent curves
plot_avg_learning_curves(polynomial_regression, 20, X, y, max_cap=5, extra_epochs=25)

# %%
ridge_reg = Ridge(alpha=1, solver="cholesky")
ridge_reg.fit(X, y)
ridge_reg.predict([[1.5]])

# %%
sgd_reg = SGDRegressor(penalty="l2")
sgd_reg.fit(X, y.ravel())
sgd_reg.predict([[1.5]])

# %%
lasso_reg = Lasso(alpha=0.1)
lasso_reg.fit(X, y)
lasso_reg.predict([[1.5]])

# %%
sgd_reg = SGDRegressor(penalty="l1")
sgd_reg.fit(X, y.ravel())
sgd_reg.predict([[1.5]])

# %%
elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5)
elastic_net.fit(X, y)
elastic_net.predict([[1.5]])

# %%
X_train, X_val, y_train, y_val = train_test_split(X, y, train_size=0.2)

# %%
poly_scaler = Pipeline([
    ("poly_features", PolynomialFeatures(degree=90, include_bias=False)),
    ("std_scaler", StandardScaler())
])
X_train_poly_scaled = poly_scaler.fit_transform(X_train)
X_val_poly_scaled = poly_scaler.fit_transform(X_val)

# %%
sgd_reg = SGDRegressor(max_iter=1, tol=-np.infty, warm_start=True, penalty=None, learning_rate="constant", eta0=0.0005)

# %%
minimum_val_error = float("inf")
best_epoch = None
best_model = None

# %%
for epoch in range(1000):
    sgd_reg.fit(X_train_poly_scaled, y_train.ravel())
    y_val_predict = sgd_reg.predict(X_val_poly_scaled)
    val_error = mean_squared_error(y_val, y_val_predict)
    if val_error < minimum_val_error:
        minimum_val_error = val_error
        best_epoch = epoch
        best_model = deepcopy(sgd_reg)

# %%
print(f"Best epoch: {best_epoch}, minimum validation error: {minimum_val_error:.2f}")

# %%
iris = datasets.load_iris()
print(list(iris.keys()))

# %%
X = iris["data"][:, 3:]
y = (iris["target"] == 2).astype(np.int32)

# %%
log_reg = LogisticRegression(n_jobs=32)
log_reg.fit(X, y)

# %%
X_new = np.linspace(0, 3, 1000).reshape(-1, 1)
y_proba = log_reg.predict_proba(X_new)
plt.plot(X_new, y_proba[:, 1], "g-", label="Iris virginica")
plt.plot(X_new, y_proba[:, 0], "b--", label="Not Iris virginica")
plt.show()

# %%
print("Predictions for 1.7cm and 1.5cm:", log_reg.predict([[1.7], [1.5]]))

# %%
X = iris["data"][:, (2, 3)]
y = iris["target"]

# %%
softmax_reg = LogisticRegression(multi_class="multinomial", solver="lbfgs", C=10)
softmax_reg.fit(X, y)

# %%
print(softmax_reg.predict([[5, 2]]), softmax_reg.predict_proba([[5, 2]]))

# %%


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy import stats
from scipy.io import loadmat

data = loadmat('data/ex8data1.mat')
x = data['X']
x_val = data['Xval']
y_val = data['yval'][:,0]
print(x.shape)

def findGaussian(x):
	return x.mean(axis=0), x.var(axis=0)

def probabilities(x, mean, var):
	#x is an m*n dimensional matrix
	m = x.shape[0]
	n = x.shape[1]
	p = np.ones(x.shape)
	for i in range(n):
		tmp = stats.norm(mean[i], var[i])
		p[:,i] = tmp.pdf(x[:,i])
	prob = np.prod(p, axis=1)
	return prob

def fScore(y_pred, y_val):
	#tp, tn, fn, fp are true positive, true negative, false negative, false positive
	tp = np.sum(np.logical_and(y_pred == 1, y_val == 1)).astype(float)
	tn = np.sum(np.logical_and(y_pred == 0, y_val == 0)).astype(float)
	fn = np.sum(np.logical_and(y_pred == 0, y_val == 1)).astype(float)
	fp = np.sum(np.logical_and(y_pred == 1, y_val == 0)).astype(float)
	#precision and recall
	if (tp + fp == 0 or tp + fn == 0):
		return -1
	p = tp / (tp + fp)
	r = tp / (tp + fn)
	f_score = (2 * p * r) / (p + r)
	#print(f_score)
	return f_score

def findThreshold(x_val, y_val, mean, var):
	prob = probabilities(x_val, mean, var)
	epsilon_best, f_best = 0, 0
	step = (prob.max() - prob.min()) / 1000
	for i in np.arange(prob.min(), prob.max(), step):
		epsilon = i
		y_pred = np.array([1 if el < epsilon else 0 for el in prob])
		f = fScore(y_pred, y_val)
		if (f > f_best):
			epsilon_best, f_best = epsilon, f
	return epsilon_best, f_best

mean, var = findGaussian(x)
epsilon, f = findThreshold(x_val, y_val, mean, var)
prob = probabilities(x, mean, var)
print(epsilon, f)
#print(epsilon, f)
outliers = np.array([x[i] for i in range(x.shape[0]) if (prob[i] < epsilon)])
print(outliers.shape)

#fig, ax = plt.subplots(figsize=(12,8))
#ax.scatter(x[:,0], x[:,1])
#ax.scatter(outliers[:,0], outliers[:,1])
#plt.show()


# Polynomial regression

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

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.compose import ColumnTransformer

dataset = pd.read_csv('Position_Salaries.csv')
X = dataset.iloc[:, 1:-1].values
y = dataset.iloc[:, -1].values

# Train simple linear regression model

lr = LinearRegression()
lr.fit(X, y)

# Polynomial features

from sklearn.preprocessing import PolynomialFeatures
pf = PolynomialFeatures(degree = 4)
X_poly = pf.fit_transform(X)
pr = LinearRegression()
pr.fit(X_poly, y)

# Visualizing the data

y_hat = lr.predict(X)
y_hat_poly = pr.predict(pf.fit_transform(X))
plt.scatter(X, y, color='red')
plt.plot(X, y_hat, color='blue')
plt.plot(X, y_hat_poly, color='green')
plt.title('Salary vs Position')
plt.xlabel('Position')
plt.ylabel('Salary')
plt.show()

# Prediction for other values

# Making a single prediction

position = 6.5
prediction = pr.predict(pf.fit_transform([[position]]))[0]
print(f'Prediction for position {position}: {prediction}')

# Evaluate model performance:

from sklearn.metrics import r2_score
score = r2_score(y, y_hat_poly)
print(f'r2 score: {score}')


"""
Please see
https://computationalmindset.com/en/neural-networks/ordinary-differential-equation-solvers.html#sys1
for details
"""

import numpy as np
import matplotlib.pyplot as plt

import torch
from torchdiffeq import odeint

def ode_sys(t, X):
	x=torch.Tensor([X[0]])
	dx_dt=torch.Tensor([X[1]])
	d2x_dt2=torch.Tensor([-dx_dt - 2*x])
	return torch.cat([dx_dt, d2x_dt2])

an_sol_x = lambda t : \
	np.exp(-t/2.) * (np.cos(np.sqrt(7) * t / 2.) + \
	np.sin(np.sqrt(7) * t / 2.)/np.sqrt(7.))

t_begin=0.
t_end=12.
t_nsamples=100
t_space = np.linspace(t_begin, t_end, t_nsamples)
x_init = torch.Tensor([1.])
dxdt_init = torch.Tensor([0.])

x_an_sol = an_sol_x(t_space)

num_sol = odeint(ode_sys, torch.cat([x_init, dxdt_init]), torch.Tensor(t_space)).numpy()

plt.figure()
plt.plot(t_space, x_an_sol, '--', linewidth=2, label='analytical')
plt.plot(t_space, num_sol[:,0], linewidth=1, label='numerical')
plt.title('ODE 2nd order IVP solved by TorchDiffEq')
plt.xlabel('t')
plt.ylabel('x')
plt.legend()
plt.show()


import tensorflow as tf
import numpy as np
import math
"""

Exercise 1.1: Diagonal Gaussian Likelihood

Write a function which takes in Tensorflow symbols for the means and
log stds of a batch of diagonal Gaussian distributions, along with a
Tensorflow placeholder for (previously-generated) samples from those
distributions, and returns a Tensorflow symbol for computing the log
likelihoods of those samples.

"""

def gaussian_likelihood(x, mu, log_std):
    """
    Args:
        x: Tensor with shape [batch, dim]
        mu: Tensor with shape [batch, dim]
        log_std: Tensor with shape [batch, dim] or [dim]

    Returns:
        Tensor with shape [batch]
    """
    #######################
    #                     #
    #   YOUR CODE HERE    #
    #                     #
    #######################
#My new answer
    presum = -0.5 * (((x - mu)/(tf.exp(log_std)))**2 + 2*log_std + np.log(2*np.pi))
    return tf.reduce_sum(presum, axis = 1)
#My old answer
'''
    # 1. Take in values x, mu, and log_std

    batch_size = 32
    dim = 10

    # Calculate std
    std = tf.math.exp(log_std)

    # Set up tf placeholders
    ans = tf.placeholder(tf.float32, shape=[dim,])
    x2 = tf.placeholder(tf.float32, shape=[dim,])
    mu2 = tf.placeholder(tf.float32, shape=[dim,])
    std2 = tf.placeholder(tf.float32, shape=[dim,])
    log_std2 = tf.placeholder(tf.float32, shape=[dim,])
    output = tf.placeholder(tf.float32, shape=[dim,])

    # Set up array for output
    output_np = []

    # 2. Calculate log_likelihood according to spinning up formula
    for i in range(batch_size):
        # intialize variables
        x2 = x[i, :]
        mu2 = mu[i, :]
        std2 = std[:]
        log_std2 = log_std[:]

        # calculate sum portion of likelihood
        A1 = x2 - mu2
        A = tf.pow(A1, 2)
        #test = (x[i, :] - mu[i, :])**2
        #test = tf.keras.backend.print_tensor(test, message = 'test is')
        #A1 = tf.keras.backend.print_tensor(A1, message = 'A1 is')
        #A = tf.keras.backend.print_tensor(A, message = 'A is')
        B1 = tf.pow(std2, 2)
        B = tf.divide(A, B1)
        #B = tf.keras.backend.print_tensor(B, message = 'B is')
        C = B + 2*log_std2
        D = tf.reduce_sum(C)
        E = -0.5*(D + dim * tf.math.log(2*math.pi))

        output_np.append(E)

    output = output_np
    return
'''

if __name__ == '__main__':
    """
    Run this file to verify your solution.
    """
    from spinup.exercises.problem_set_1_solutions import exercise1_1_soln
    from spinup.exercises.common import print_result

    sess = tf.Session()

    dim = 10
    x = tf.placeholder(tf.float32, shape=(None, dim))
    mu = tf.placeholder(tf.float32, shape=(None, dim))
    log_std = tf.placeholder(tf.float32, shape=(dim,))

    your_gaussian_likelihood = gaussian_likelihood(x, mu, log_std)
    true_gaussian_likelihood = exercise1_1_soln.gaussian_likelihood(x, mu, log_std)

    batch_size = 32
    feed_dict = {x: np.random.rand(batch_size, dim),
                 mu: np.random.rand(batch_size, dim),
                 log_std: np.random.rand(dim)}

    your_result, true_result = sess.run([your_gaussian_likelihood, true_gaussian_likelihood],
                                        feed_dict=feed_dict)

    '''
    z = tf.print(your_result)
    z2 = tf.print(true_result)
    sess.run([z, z2])
    '''
    correct = np.allclose(your_result, true_result)
    print_result(correct)


"""Module to define useful transformations."""
import numpy as np
import scipy.stats as stats
from scipy import integrate, special

def log_moments_mu(m, s):
    """Computes the mean of a log_normal distribution.

    Args:
        m: first parameter of log-normal distribution
        s: second parameter of log-normal distribution

    Returns:
        mean of the log_normal distribution
    """
    return np.exp(m + 0.5 * (s ** 2))

def log_moments_sigma(m, s):
    """Computes the variance of a log_normal distribution.

    Args:
        m: first parameter of log-normal distribution
        s: second parameter of log-normal distribution

    Returns:
      variance of the log_normal distribution
    """
    # Takes as input the parameters m and s of the log normal distribution and outputs the variance
    return np.exp(2 * m + (s ** 2)) * (np.exp(s ** 2) - 1)

def get_mape(x, x_hat):
    """Computes the Mean Absolute Percentage Error.

    Args:
        x: ground truth
        x_hat: estimates

    Returns:
        MAPE

    """
    return np.abs(x - x_hat) / x

def get_wd(m, s, m0, s0):
    """Computes 1-Wasserstein distance between the two normals.

    Args:
        m: mean of first normal distribution
        s: variance of first normal distribution
        m0: mean of second normal distribution
        s0: variance of second normal distribution

    Returns:
        1-Wasserstein distance between the two normals

    """

    def f(x):
        np.abs(m - m0 + np.sqrt(2) * (s - s0) * special.erfinv(x))

    return integrate.quad(f, 0, 1)

def visibility(m, s, fm):
    """Computes the probability mass of a normal distribution in a upper bounded domain.

    Args:
        m: mean of first normal distribution
        s: variance of first normal distribution
        fm: fluorescence upper bound

    Returns:
        Computes the probability mass up to fm of the normal distribution parameterised by m and s
    """
    return stats.norm.cdf(fm, loc=m, scale=s) - stats.norm.cdf(0, loc=m, scale=s)

def ab_to_ms(a, b):
    """Convert shape and scale of gamma distribution to mean and standard deviation.

    Args:
        a: shape of gamma distribution
        b: scale of gamma distribution

    Returns:
        mean and standard deviation
    """
    return np.array([a * b, np.sqrt(a) * b])

def ms_to_ab(m, s) -> np.ndarray:
    """Convert mean and standard deviation of gamma distribution to shape and scale.

    Args:
        m: mean
        s: standard deviation

    Returns:
        shape and scale parameters
    """
    # takes as input the mean and standard deviation of the gamma dsitribution and return shape and scale parameters
    return np.array([(m / s) ** 2, s ** 2 / m])


from typing import Callable

import numpy as np

def composite_trapezoid(f_: 'Callable[float]', a: float, b: float, n: float) -> float:
    """Computes the analitical solution of the integral of f from a to b
    following the composite trapezoidal rule.

    Args:
        f_ (Callable[float]): Function to be integrated.
        a (float): Lower bound of hte interval.
        b (float): Upper bound of the interval.
        n (float): The number of parts the interval is divided into.

    Returns:
        float: Numerical solution of the integral.
    """
    x = np.linspace(a, b, n + 1)
    f = f_(x)
    h = (b - a) / (n)

    return h/2 * sum(f[:n] + f[1:n+1])

def composite_simpson(f_: 'Callable[float]', a: float, b: float, n: float) -> float:
    """Computes the analitical solution of the integral of f from a to b
    following the composite Simpson's 1/3 rule.

    Args:
        f_ (Callable[float]): Function to be integrated.
        a (float): Lower bound of hte interval.
        b (float): Upper bound of the interval.
        n (float): The number of parts the interval is divided into.

    Returns:
        float: Numerical solution of the integral.
    """
    x = np.linspace(a, b, n+1)
    f = f_(x)
    h = (b - a) / (n)

    return (h/3) * (f[0] + 2*sum(f[2:n-1:2]) + 4*sum(f[1:n:2]) + f[n])


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

def pi_by_dice(n):
    k = 0
    for _ in range(n):
        x, y = random.random(), random.random()
        if x * x + y * y <= 1:
            k = k + 1
    return 4 * k * 1.0 / n

for i in range(1, 8):
    v = 10 ** i
    print('pi({0}) = {1}'.format(v, pi_by_dice(v)))

n = 100000
pi_by_series = np.sum(4.0 / np.r_[1:n:4, -3:-n:-4])

print("pi_by_series =", pi_by_series)


import numpy as np
import networkx as nx
import scipy.sparse.linalg

def spectralgraphdistance(G1, G2):
    """
    Compute the distance between G1 and G2, defined as || L1 - L2 ||_2,
    where L1 and L2 are the unweighted graph Laplacian matrices corresponding
    to G1 and G2, and || . ||_2 is the induced matrix 2-norm, (which is the
    spectral radius for symmetric matrices like ours.)

    This distance metric assumes that G1 and G2 are defined over the same
    vertex set.

    Note: This is all dense matrix math. Of course, sparse matrix work is key
        for larger problem sizes. Cross that bridge if this works and it seems
        worth it. (scipy.sparse.csgraph has a graph laplacian function)

    """
    # Check precondition: same vertex set
    V1 = G1.nodes()
    V2 = G2.nodes()
    if len(set(V1).intersection(set(V2))) != len(V1):
        raise ValueError("graphs G1 and G2 need to have the same vertex set.")

    # Get the graph Laplacians. Use the node ordering of G1.
    # need to upcast the type? https://stackoverflow.com/questions/8650014/sparse-matrix-valueerror-matrix-type-must-be-f-d-f-or-d
    L1 = nx.laplacian_matrix(G1, nodelist=V1).asfptype()
    L2 = nx.laplacian_matrix(G2, nodelist=V1).asfptype()

    # Compute spectral radius using ARPACK / Lanczos iteration
    lambda_max = scipy.sparse.linalg.eigsh(L1 - L2, k=1, which='LM', return_eigenvectors=False)[0]
    return abs(lambda_max)

def _testcase():
    G1 = nx.gnp_random_graph(20, 0.2)
    G2 = nx.gnp_random_graph(20, 0.2)
    d = spectralgraphdistance(G1, G2)
    print('graph distance:', d)

if __name__ == '__main__':
    _testcase()

import numpy,import scipy,import networkx


# See https://en.wikipedia.org/wiki/Limit_(mathematics)

from sympy import sin, limit, oo
from sympy.abc import x

if __name__ == "__main__":
    cases = [
        "limit(sin(x)/x, x, oo)",
        "limit(1/x, x, 0)",
        "limit(1/x, x, 0, dir='-')"
    ]

    for c in cases:
        print('[] :python-answer """%s = %s""".' % (c, eval(c)))


## 2. The Empirical Probability ##

p_tail = (100-56) / 100
p_six = 28 / 200
p_odd = 102 / 200

print(p_tail)
print(p_six)
print(p_odd)

## 3. Probability as Relative Frequency ##

p_heads_1 = (300-162) / 300
percentage_1 = p_heads_1 * 100

p_heads_2 = (5000-2450) / 5000
percentage_2 = p_heads_2 * 100

## 4. Repeating an Experiment ##

# INITIAL CODE
from numpy.random import seed, randint

seed(1)

def coin_toss():
    if randint(0,2) == 1:
        return 'HEAD'
    else:
        return 'TAIL'

probabilities = []
heads = 0

#for n in range(1, 10001):
    # Uncomment above and complete code from here
# SOLUTION CODE
for n in range(1, 10001):
    outcome = coin_toss()
    if outcome == 'HEAD':
        heads += 1
    current_probability = heads / n
    probabilities.append(current_probability)

print(probabilities[:10])
print(probabilities[-10:])

## 5. The True Probability Value ##

p_l = 87 / 200
p_l_and_c = 40 / 200
p_h = 63 / 200
p_no = (200-160) / 200

## 6. The Theoretical Probability ##

p_5 = 1 / 6
p_ht = 1 / 4
p_tt = 1 / 4

## 7. Events vs. Outcomes ##

p_even = 3 / 6
p_odd_no_3 = 2 / 6
p_odd_greater_5 = 0 / 6

## 8. A Biased Die ##

p_blue = 10 / 100
p_red = 90 / 100

Data Scientist In Python/Step 5 - Probability and Statistics/3. Probability Fundamentals/1. Estimating Probabilities.py


"""
A type of pseudo-random number generator is the so-called lagged fibonacci generator
[http://en.wikipedia.org/wiki/Lagged_fibonacci_generator], which has become somewhat popular because it is very simple
to implement, can have an extremely long period, and produces high quality random numbers.

The idea is this: to calculate s(n) (i.e. the nth random number), you evaluate:
s(n) = (s(n - a) + s(n - b)) mod M

For some positive constants a and b (it is thus similar to the fibonacci numbers, but it "lags" behind) and some
modulus M. One popular choice for a and b is a = 24 and b = 55. Lets use those numbers and a modulus of 1073741824
(i.e. 230 ), and the generator becomes:
s(n) = (s(n - 24) + s(n - 55)) mod 1073741824

In order for this formula to work, you need to initialize the values s(0),s(1),...,s(54), so that the recursion has
somewhere to bottom out. Often, another random number generator is used to supply the initial values. Lets use the
random number generator from the intermediate challenge #53
[http://www.reddit.com/r/dailyprogrammer/comments/tpxqc/5162012_challenge_53_intermediate/].

That is to say, for values s(0) through s(54), s is defined as follows:
s(0) = 123456789
s(n) = (22695477 * s(n-1) + 12345) mod 1073741824

But for values s(55) and above, s is defined as follows:
s(n) = (s(n - 24) + s(n - 55)) mod 1073741824

Here are a few example values:
s(10)     = 1048156299
s(20)     = 472459921
s(55)     = 827614689
s(56)     = 530449927
s(100)    = 515277845
s(1000)   = 985063932
s(10000)  = 304605728
s(100000) = 434136346

Find s( 10 ** 18 )
"""

import numpy as np

A = 7
B = 10
M = 2 ** 30
d = {}

def s_init(n, sp):
    """ from 20120516B """
    if n == 0:
        return 123456789
    else:
        return (22695477 * sp + 12345) % M

def s(n, list):
    if n < B:
        return list[n]
    return (s(n-A, list) + s(n-B, list)) % M

def d_s(n):
    if n < B:
        return d[n]
    if n-A in d:
        s_a = d[n-A]
    else:
        s_a = d_s(n-A)
        d[n-A] = s_a
    if n-B in d:
        s_b = d[n-B]
    else:
        s_b = d_s(n-B)
        d[n-B] = s_b
    return (s_a + s_b) % M

def gen_A():
    big = max(A, B)
    res = np.concatenate(([np.zeros(big)], np.identity(big)[:-1]), axis=0)
    res[0][A-1] = 1
    res[0][B-1] = 1
    return res

def gen_X():
    big = max(A, B)
    res = np.zeros(big, dtype='object')
    res[0] = s_init(0, -1)
    for i in range(1, big):
        res[i] = s_init(i, res[i-1])
    return res[::-1]

def lfg_main(n):
    big = max(A, B)
    A_mat = gen_A()
    X_mat = gen_X()

    if n < big:
        print(X_mat[big-n-1])
    else:
        m = n-big+1
        A_mat = np.linalg.matrix_power(A_mat, m)
        print(m)
        for a in A_mat:
            for b in a:
                print(b, type(b))
        X_out = np.remainder(np.dot(A_mat, X_mat), M)
        print(int(X_out[0]))

def d_main(n):
    d[0] = s_init(0, -1)
    for i in range(1, B):
        d[i] = s_init(i, d[i-1])

    print(d_s(n))

def main(n):
    l = [s_init(0, -1)]
    for i in range(1, B):
        l.append(s_init(i, l[-1]))

    print(s(n, l))

if __name__ == "__main__":
    n = 1000
    # main()
    # d_main(n)
    lfg_main(n)


import numpy as np

class LogisticRegression():
    def __init__(self, learnrate=0.01, eps=0.001, maxiter = 2000000):
        self.learnrate = learnrate
        self.eps = eps
        self.maxiter = maxiter

    def _sigmoid(self, x):
        return 1.0 / (1 + np.exp(-x))

    def _predict_point(self, x):
        theta = self._sigmoid(self.w * x)
        if theta > 0.5:
            return 1
        return 0

    def _predict_proba(self, X):
        print("Dot product:" + str(X @ self.w))
        prob_class_1 = self._sigmoid(X @ self.w)
        return np.hstack((prob_class_1, 1 - prob_class_1))

    def predict_proba(self, X):
        bias = np.ones((X.shape[0], 1))
        X = np.hstack((bias, X))
        return self._predict_proba(X)

    def predict(self, X):
        bias = np.ones((X.shape[0], 1))
        X = np.hstack((bias, X))
        prob_class_1 = self._predict_proba(X)[:,0:1]
        return (prob_class_1 > 0.5).astype(int)

    def _gradientloss(self, w, X, y):
        m = X.shape[0]
        return ((self._sigmoid(X @ w) - y ).T @ X)/m

    def _gradientdescent(self, w0, learnrate, grad, eps):
        w = w0
        for i in range(self.maxiter):
            w_new = w - learnrate * grad(w)
            if np.linalg.norm(w_new - w) < eps:
                return w
            w = w_new
        return w

    def fit(self, X, y):
        bias = np.ones((X.shape[0], 1))
        X = np.hstack((bias, X))
        try: # Check for existence of the weight vector
            self.w
        except AttributeError:
            self.w = np.random.rand(X.shape[1],1)
        self.w = self._gradientdescent(self.w, self.learnrate, lambda w : self._gradientloss(w, X, y), self.eps)


# -*- coding: utf-8 -*-
"""
Created on Sat Sep 18 02:53:27 2021

@author: Mahfuz_Shazol
"""

import numpy as np
import torch as th
A=np.array([
    [-1.,4],
    [2,-2]
    ])

A_th=th.as_tensor(A);

eigons=th.eig(A_th,eigenvectors=True)
print('lambdas',eigons)

v_th=eigons.eigenvectors[:,0]
lmbda=eigons.eigenvalues[0][0]
print('lmbda',lmbda)

#A**v==Lamda**v
r1=th.matmul(A_th,v_th)
print('A**v=',r1)

r2=lmbda*v_th
print(r2)


import numpy as np

perch_length = np.array([8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0, 21.0,
       21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5, 22.5, 22.7,
       23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5, 27.3, 27.5, 27.5,
       27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0, 36.5, 36.0, 37.0, 37.0,
       39.0, 39.0, 39.0, 40.0, 40.0, 40.0, 40.0, 42.0, 43.0, 43.0, 43.5,
       44.0])
perch_weight = np.array([5.9, 32.0, 40.0, 51.5, 70.0, 100.0, 78.0, 80.0, 85.0, 85.0, 110.0,
       115.0, 125.0, 130.0, 120.0, 120.0, 130.0, 135.0, 110.0, 130.0,
       150.0, 145.0, 150.0, 170.0, 225.0, 145.0, 188.0, 180.0, 197.0,
       218.0, 300.0, 260.0, 265.0, 250.0, 250.0, 300.0, 320.0, 514.0,
       556.0, 840.0, 685.0, 700.0, 700.0, 690.0, 900.0, 650.0, 820.0,
       850.0, 900.0, 1015.0, 820.0, 1100.0, 1000.0, 1100.0, 1000.0,
       1000.0])

from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = train_test_split(perch_length, perch_weight, random_state=42)
print(train_input.shape, test_input.shape)

train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)
print(train_input.shape, test_input.shape)

import matplotlib.pyplot as plt

plt.scatter(train_input, train_target)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor(n_neighbors=3)
knr.fit(train_input, train_target)

print(knr.predict([[50]]))

distances, indexes = knr.kneighbors([[50]])

plt.scatter(train_input, train_target)
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
plt.scatter(50, 1033, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

print(np.mean(train_target[indexes]))

print(knr.predict([[100]]))

distances, indexes = knr.kneighbors([[100]])

plt.scatter(train_input, train_target)
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
plt.scatter(100, 1033, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

from sklearn.linear_model import LinearRegression

lr = LinearRegression()
lr.fit(train_input, train_target)

print(lr.predict([[50]]))

print(lr.coef_, lr.intercept_)

plt.scatter(train_input, train_target)
plt.plot([15, 50], [15 * lr.coef_ + lr.intercept_, 50 * lr.coef_ + lr.intercept_])
plt.scatter(50, 1241.8, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

print(lr.score(train_input, train_target))
print(lr.score(test_input, test_target))

train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))
print(train_poly.shape, test_poly.shape)

lr.fit(train_poly, train_target)
print(lr.predict([[50 ** 2, 50]]))

print(lr.coef_, lr.intercept_)

point = np.arange(15, 50)

plt.scatter(train_input, train_target)
plt.plot(point, 1.01 * point ** 2 - 21.6 * point + 116.05)
plt.scatter(50, 1574, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))


# ============================================= #
# Módulo de suporte para realização das tarefas #
# ============================================= #

import numpy as np

# ============================================= #
# Algoritmo QR                                  #
# ============================================= #

def sign(x: float) -> float:
    """
    Função sinal: retorna 1.0 se x é maior ou igual a zero, -1.0 caso contrário.

    Args:
        x (float): valor a ser comparado com zero.

    Returns:
        float: 1.0 ou -1.0, a depender do valor de x.
    """

    if x >= 0.0:
        return 1.0
    else:
        return -1.0

def wilkinson_shift(alpha: float, beta: float, alpha_last: float) -> float:
    """
    Calcula o valor do deslocamento espectral de acordo com a heurística de Wilkinson.

    Args:
        alpha (float): (n-1)-ésimo elemento da diagonal principal.
        beta (float): n-ésimo elemento da diagonal abaixo da principal.
        alpha_last (float): n-ésimo elemento da diagonal principal.

    Returns:
        float: valor do deslocamento espectral.
    """

    d = (alpha - alpha_last) / 2.0
    mu = alpha_last + d - sign(d) * np.sqrt(d**2 + beta**2)

    return mu

def cos_and_sin(alpha: float, beta: float) -> "tuple[float, float]":
    """
    Calcula os valores de cosseno e seno que compõem uma dada matriz de rotação de Givens.
    Utilizou-se o "método numericamente mais estável" tal como descrito no enunciado.

    Args:
        alpha (float): (n-1)-ésimo elemento da diagonal principal.
        beta (float): n-ésimo elemento da diagonal abaixo da principal.

    Returns:
        tuple: valor do cosseno (primeira posição) e do seno (segunda posição) em uma tupla.
    """

    if np.abs(alpha) > np.abs(beta):
        tau = - beta / alpha
        c = 1.0 / np.sqrt(1.0 + tau**2)
        s = c * tau
    else:
        tau = - alpha / beta
        s = 1.0 / np.sqrt(1.0 + tau**2)
        c = s * tau

    return (c, s)

def givens_matrix(n: int, i: int, c: float, s: float) -> np.ndarray:
    """
    Gera a matriz de rotação de Givens para duas linhas consecutivas doutra matriz.

    Args:
        n (int): dimensão da matriz a sofrer rotação (igual a dimensão da matriz de Givens).
        i (int): índice da linha a sofrer rotação com i+1.
        c (float): valor do cosseno calculado sobre alpha e beta.
        s (float): valor do seno calculado sobre alpha e beta.

    Returns:
        np.ndarray: matriz de rotação de Givens de ordem n.
    """

    G = np.eye(n)

    G[i, i] = c
    G[i+1, i+1] = c

    G[i, i+1] = -s
    G[i+1, i] = s

    return G

def givens_rotation(A: np.ndarray) -> "tuple[np.ndarray, np.ndarray]":
    """
    Realiza um passo da decomposição QR em matrizes tridiagonais simétricas por meio da rotação de Givens.

    Args:
        A (np.ndarray): matriz tridiagonal simétrica a ser decomposta em Q (ortonormal) e R (triangular superior).

    Returns:
        tuple: uma tupla com as matrizes Q (primeira posição) e R (segunda posição).
    """

    n = A.shape[0]
    Q = np.eye(n) # Inicializa a matriz ortogonal
    R = np.copy(A) # Operações não in-place

    # Itera ao longo da diagonal principal (crescente)
    for m in range(0, n-1, 1):
        # Gera a matriz de rotação
        c, s = cos_and_sin(R[m, m], R[m+1, m])
        G = givens_matrix(n, m, c, s)

        # Calcula Q e R
        Q = Q @ G.T
        R = G @ R

    return (Q, R)

def QR(A0: np.ndarray, epsilon: float=1e-6, shifted: bool=True) -> "tuple[np.ndarray, np.ndarray, int]":
    """
    Por meio de decomposições QR com deslocamento espectral, calcula as matrizes Lambda e V tais que
    V @ Lambda @ V.T == A0, sendo:

    - A0: tridiagonal simétrica;
    - V: ortonormal, cujas colunas são autovetores de A0;
    - Lambda: tridiagonal simétrica, cuja diagonal principal são autovalores de A0.

    Args:
        A0 (np.ndarray): matriz tridiagonal simétrica a ser decomposta.
        epsilon (float): precisão mínima para determinação da convergência.
        shifted (bool): aplica (True) ou não (False) os deslocamentos espectrais.

    Returns:
        tuple: tupla com a matriz V (primeira posição), a matriz Lambda (segunda posição) e o número
        total de iterações até a convergência (terceira posição).
    """

    n = A0.shape[0]
    k = 0 # Número de iterações até a convergência de todos os elementos

    A = np.copy(A0) # Operações não in-place
    V = np.eye(n)   # Inicializa matriz de autovetores
    I = np.eye(n)

    # Itera ao longo da diagonal principal (decrescente)
    for m in range(n-1, 0, -1):
        # Loop até a convergência de beta
        while np.abs(A[m, m-1]) >= epsilon:
            mu = wilkinson_shift(A[m-1, m-1], A[m, m-1], A[m, m]) if (shifted and k > 0) else 0.0
            Q, R = givens_rotation(A - mu*I)

            # Atualiza as matrizes
            A = R @ Q + mu*I
            V = V @ Q

            k += 1 # Nova iteração

        A[m, m-1] = 0 # Quebra do laço while: beta convergiu (é nulo)

    Lambda = A

    return (V, Lambda, k)

# ============================================= #
# Tarefas                                       #
# ============================================= #

def normalize(matrix: np.ndarray) -> np.ndarray:
    """
    Normaliza as colunas de uma matriz in-place por meio da
    norma Euclidiana para um vetor do R^n.

    Args:
        matrix (np.ndarray): matriz a ter suas colunas normalizadas.

    Returns:
        np.ndarray: matriz com as colunas normalizadas.
    """

    norm = np.sqrt(np.sum(matrix**2, axis=0))
    matrix = matrix / norm

    return matrix

def gen_eign(n: int) -> "tuple[list, np.ndarray]":
    """
    Gera autovalores e autovetores (analitacamente conhecidos) para uma
    matriz tridiagonal simétrica cujos elementos da diagonal principal são
    todos iguais a 2 e das diagonais abaixo e acima dessa são iguais a -1.

    Args:
        n (int): dimensão da matriz tridiagonal simétrica.

    Returns:
        tuple: lista de autovalores da matriz (primeira posição) e uma matriz
        cujas colunas são os respectivos autovetores associados (segunda posição).
    """

    # Vetor base (constante) para calcular autovetores
    base_vec = np.arange(1, n+1, 1) * np.pi/(n+1)
    base_vec = np.reshape(base_vec, (n, 1))

    eigs_vals = [] # Armazena autovalores
    eign_vecs = [] # Armazena autovetores

    # Gera autovalores e autovetores
    for j in range(1, n+1):
        val = 2 * (1 - np.cos( j*np.pi / (n+1) ))
        vec = np.sin(base_vec * j)

        eigs_vals.append(val)
        eign_vecs.append(vec)

    # Concatena uma lista de vetores coluna em uma matriz, normalizando-a
    eign_vecs = normalize(np.hstack(eign_vecs))

    return (eign_vecs, eigs_vals)

def gen_tridiagonal(alpha, beta, n=None) -> np.ndarray:
    """
    Gera uma matriz diagonal simétrica.

    Se alpha e beta forem listas, as insere como diagonal principal (alpha)
    e diagonais acima e abaixo dessa (beta) numa matriz de zeros.

    Se alpha e beta forem números, cria uma matriz de zeros com dimensão n
    em que os elementos da diagonal princpal são iguais a alpha e os da
    diagonais axima e abaixo dessa são iguais a beta.

    Args:
        alpha (int/float/list): elemento(s) da diagonal principal.
        beta (int/float/list): elemento(s) das diagonais abaixo e acima da principal.
        n (int/None): se int, n é a dimensão da matriz; se None, a dimensão deve
                          estar implícita em alpha e beta.

    Returns:
        np.ndarray: matriz diagonal simétrica.
    """

    assert type(alpha) == type(beta)

    if isinstance(alpha, (float, int)):
        assert n is not None

        # Cria vetores baseado nos valores passados
        alpha_vec = n * [alpha]
        beta_vec = (n-1) * [beta]

        # Insere vetores em uma matriz de zeros
        M = np.diag(beta_vec, k=-1)
        M += np.diag(alpha_vec, k=0)
        M += np.diag(beta_vec, k=1)

    elif isinstance(alpha, list):
        assert len(alpha) == len(beta)+1
        assert n is None

        # Insere vetores em uma matriz de zeros
        M = np.diag(beta, k=-1)
        M += np.diag(alpha, k=0)
        M += np.diag(beta, k=1)

    return M

# ============================================= #
# Miscelânia                                    #
# ============================================= #

def print_table(data: dict) -> None:
    """
    Imprime linha a linha um dicionário de listas formatado
    como uma tabela.

    Args:
        data (dict): dicionário de listas a ser impresso.
    """

    header_fields = list(data.keys())
    table_vals = list(data.values())

    # Células com até 10 caracteres alinhados à esquerda
    row_shape = "| {:<10}"*len(header_fields) + " |"

    header = row_shape.format(*header_fields)
    columns = np.array(table_vals).T

    print(header)            # Cabeçalho
    print('=' * len(header)) # Separador
    for row_vals in columns: # Linhas
        print(row_shape.format(*row_vals))

    return

def ctext(text: str, tag: str) -> str:
    """
    Aplica cor a uma string a ser impressa no terminal, através de tags pré-definidas.
    As cores podem sofrer alterações conforme as configurações do terminal do usuário.

    Args:
        text (str): texto a ser colorido.
        tag (str): identificador que mapeia a cor desejada a um código ASCII; tags válidas:
                   'r' --> Vermelho; 'g' --> Verde; 'y' --> Amarelo; 'b' --> Azul;
                   'm' --> Magenta; 'c' --> Ciano.

    Returns:
        str: string idêntica a text, exceto pelas tags de cor.
    """

    color_dict = {
        'r' : '\033[31m', # Red
        'g' : '\033[32m', # Green
        'y' : '\033[33m', # Yellow
        'b' : '\033[34m', # Blue
        'm' : '\033[35m', # Magenta
        'c' : '\033[36m'  # Cyan
    }

    # Aplica a tag de cor e reseta para a cor padrão do terminal do usuário.
    text = color_dict[tag] + text + '\033[0m'

    return text


import numpy as np
import math

def find_rectangle(n):
    max_side = int(math.sqrt(n))
    for h in range(2, max_side+1)[::-1]:
        w = n // h
        if (w * h) == n:
            return (w,h)
    return (n, 1)

# should work for 1d and 2d images, assumes images are square but can be overriden
def make_mosaic(images, n=None, nx=None, ny=None, w=None, h=None):
    if n is None and nx is None and ny is None:
        nx, ny = find_rectangle(len(images))
    else:
        nx = n if nx is None else nx
        ny = n if ny is None else ny
    images = np.array(images)
    if images.ndim == 2:
        side = int(np.sqrt(len(images[0])))
        h = side if h is None else h
        w = side if w is None else w
        images = images.reshape(-1, h, w)
    else:
        h = images.shape[1]
        w = images.shape[2]
    nx = int(nx)
    ny = int(ny)
    h = int(h)
    w = int(w)
    image_gen = iter(images)
    # should replace this code with https://stackoverflow.com/a/42041135/940196
    if len(images.shape) > 3:
        mosaic = np.empty((h*ny, w*nx, images.shape[3]))
    else:
        mosaic = np.empty((h*ny, w*nx))
    for i in range(ny):
        ia = (i)*h
        ib = (i+1)*h
        for j in range(nx):
            ja = j*w
            jb = (j+1)*w
            mosaic[ia:ib, ja:jb] = next(image_gen)
    return mosaic


import numpy as np

# Write a function that takes as input two lists Y, P,
# and returns the float corresponding to their cross-entropy.
def cross_entropy(Y, P):
    cross_entropy = 0.0
    for i in range(len(Y)):
        cross_entropy -= Y[i]*np.log(P[i]) + (1 - Y[i])*(np.log(1 - P[i]))
    return cross_entropy

#or

'''def cross_entropy(Y, P):
    Y = np.float_(Y)
    P = np.float_(P)
    return -np.sum(Y * np.log(P) + (1 - Y) * np.log(1 - P))'''


"""
int_{0}^{pi/2} sin^2(x) dx
Calculate this integral numerically, using the trapezoidal rule

int_{0}^{1/pi} sin(1/x) dx
Calculate this integral numerically, using the Gaussian quadrature method
"""
from math import sin, pi

import numpy as np

def integral_trapezoidal_rule(f, a: float, b: float, n: int) -> float:
    area = 0
    dx = (b - a) / n
    h1 = f(0)
    for i in range(1, n + 1):
        h2 = f(i * dx)
        area += 1/2 * (h1 + h2) * dx
        h1 = h2
    return area

def integral_rectangles(f, a: float, b: float, n: int) -> float:
    area = 0
    dx = (b - a) / n
    for i in range(0, n):
        h = f(i * dx)
        area += h * dx
    return area

def integral_gaussian_quadrature(f, a: float, b: float, deg: int) -> float:
    # deg: number of sample points and weights
    x, w = np.polynomial.legendre.leggauss(deg)
    # map [-1, 1] interval to [a, b]
    u = 0.5 * (x + 1) * (b - a) + a
    return sum(w * f(u)) * 0.5 * (b - a)

def func_a(x: float) -> float:
    return sin(x) ** 2

def func_b(x: float) -> float:
    return np.sin(1/x)

print('Expected value of int of (sin x)^2 from 0 to PI/2: PI/4 ~ 0.78539')
print('For 1% acurracy: 0.77754 < x < 0.79325')
n = 80
print(integral_trapezoidal_rule(func_a, 0, pi/2, n))
print(integral_rectangles(func_a, 0, pi/2, n))
print('Exp. val. of int of sin(1/x) from 0 to 1/PI: -Ci(PI) ~ -0.07366')
print(integral_gaussian_quadrature(func_b, 0, 1/pi, 1))


"""
Posterior sampling for Gaussian Mixture Model using Gibbs sampler
"""
import numpy as np
import scipy.stats as st
import matplotlib.pyplot as plt

K = 2

# Generate data
X1 = np.random.multivariate_normal([5, 5], np.diag([0.5, 0.5]), size=20)
X2 = np.random.multivariate_normal([8, 8], np.diag([0.5, 0.5]), size=20)
X = np.vstack([X1, X2])

N = X.shape[0]

# GMM params
mus = np.array([[1, 1], [15, 15]], dtype='float')
sigmas = np.array([np.diag([1, 1]), np.diag([1, 1])], dtype='float')
lambdas = np.array([np.linalg.inv(sigmas[0]), np.linalg.inv(sigmas[1])])
pis = np.array([0.5, 0.5])  # Mixing probs.
zs = np.zeros([N])  # Assignments

# Priors
alpha = np.array([1, 1])
pis = np.random.dirichlet(alpha)
mus0 = np.array([[1, 1], [1, 1]], dtype='float')
sigmas0 = np.array([np.diag([1, 1]), np.diag([1, 1])], dtype='float')
lambdas0 = np.array([np.linalg.inv(sigmas0[0]), np.linalg.inv(sigmas0[1])])

# Gibbs sampler
for it in range(50):
    # Sample from full conditional of assignment
    # z ~ p(z) \propto pi*N(y|pi)
    probs = np.zeros([N, K])

    for k in range(K):
        p = pis[k] * st.multivariate_normal.pdf(X, mean=mus[k], cov=sigmas[k])
        probs[:, k] = p

    # Normalize
    probs /= np.sum(probs, axis=1)[:, np.newaxis]

    # For each data point, draw the cluster assignment
    for i in range(N):
        z = np.random.multinomial(n=1, pvals=probs[i]).argmax()
        zs[i] = z

    # Sample from full conditional of cluster parameter
    # Assume fixed covariance => posterior is Normal
    # mu ~ N(mu, sigma)
    Ns = np.zeros(K, dtype='int')

    for k in range(K):
        # Gather all data points assigned to cluster k
        Xk = X[zs == k]
        Ns[k] = Xk.shape[0]

        # Covariance of posterior
        lambda_post = lambdas0[k] + Ns[k]*lambdas[k]
        cov_post = np.linalg.inv(lambda_post)

        # Mean of posterior
        left = cov_post
        right = lambdas0[k] @ mus0[k] + Ns[k]*lambdas[k] @ np.mean(Xk, axis=0)
        mus_post = left @ right

        # Draw new mean sample from posterior
        mus[k] = st.multivariate_normal.rvs(mus_post, cov_post)

    # Sample from full conditional of the mixing weight
    # pi ~ Dir(alpha + n)
    pis = np.random.dirichlet(alpha + Ns)

# Expected output:
# ----------------
# 20 data in cluster-0, mean: [ 5  5 ]
# 20 data in cluster-1, mean: [ 8  8 ]
for k in range(K):
    print('{} data in cluster-{}, mean: {}'.format(Ns[k], k, mus[k]))


from numpy import *

def compute_errors_for_given_points():
    total_error = 0
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        total_error += (y - (m * x + b)) ** 2
    return total_error / float(len(points))

def step_gradient(b_current, m_current, points, learning_rate):
    # gradient descent
    b_gradient = 0
    m_gradient = 0
    N = float(len(points))
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        b_gradient += (y - (m_current * x + b_current)) * (-2 / N)
        m_gradient += (y - (m_current * x + b_current)) * (-2 / N) * x
    new_b = b_current - (learning_rate * b_gradient)
    new_m = m_current - (learning_rate * m_gradient)
    return [new_b, new_m]

def gradient_descent_runner(points, initial_b, initial_m, learning_rate, num_iterations):
    b = initial_b
    m = initial_m
    for i in range(num_iterations):
        b, m = step_gradient(b, m, array(points), learning_rate)
    return [b, m]

def run():
    points = genfromtxt('data.csv', delimiter=',')
    # hyper parameter : à quelle vitesse le modèle apprend
    learning_rate = 0.0001

    # y = mx + b
    initial_b = 0
    initial_m = 0
    num_iterations = 1000
    [b, m] = gradient_descent_runner(points, initial_b, initial_m, learning_rate, num_iterations)
    print(b)
    print(m)

if __name__ == '__main__':
    run()


import numpy as np
import pytest
from pytest import approx

from rethinking.utils import information_entropy, kl_divergence

@pytest.mark.parametrize(
    'p,expected',
    [(np.array([.3, .7]), 0.610864), (np.array([.7, .15, .15]), 0.818808), (np.array([.7, .15, .15, .0]), 0.818808)],
    # according to l'hopital rule, 0 * log(0) = 0
)
def test_information_entropy(p, expected):
    assert approx(information_entropy(p=p), rel=1e-3) == expected

@pytest.mark.parametrize(
    'p,q,expected',
    [
        (np.array([.3, .7]), np.array([.3, .7]), 0),
        (np.array([.3, .7]), np.array([.01, .99]), 0.777722),
    ],
)
def test_kl_divergence(p, q, expected):
    assert approx(kl_divergence(p, q), rel=1e-3) == expected


import tensorflow as tf
import numpy
import matplotlib.pyplot as plt

# Train a data set

size_data = numpy.asarray([ 2104,  1600,  2400,  1416,  3000,  1985,  1534,  1427,
  1380,  1494,  1940,  2000,  1890,  4478,  1268,  2300,
  1320,  1236,  2609,  3031,  1767,  1888,  1604,  1962,
  3890,  1100,  1458,  2526,  2200,  2637,  1839,  1000,
  2040,  3137,  1811,  1437,  1239,  2132,  4215,  2162,
  1664,  2238,  2567,  1200,   852,  1852,  1203 ])
price_data = numpy.asarray([ 399900,  329900,  369000,  232000,  539900,  299900,  314900,  198999,
  212000,  242500,  239999,  347000,  329999,  699900,  259900,  449900,
  299900,  199900,  499998,  599000,  252900,  255000,  242900,  259900,
  573900,  249900,  464500,  469000,  475000,  299900,  349900,  169900,
  314900,  579900,  285900,  249900,  229900,  345000,  549000,  287000,
  368500,  329900,  314000,  299000,  179900,  299900,  239500 ])

# Test a data set

size_data_test = numpy.asarray([ 1600, 1494, 1236, 1100, 3137, 2238 ])
price_data_test = numpy.asarray([ 329900, 242500, 199900, 249900, 579900, 329900 ])

def normalize(array):
    return (array - array.mean()) / array.std()

# Normalize a data set

size_data_n = normalize(size_data)
price_data_n = normalize(price_data)

size_data_test_n = normalize(size_data_test)
price_data_test_n = normalize(price_data_test)

# Display a plot
plt.plot(size_data, price_data, 'ro', label='Samples data')
plt.legend()
plt.draw()

samples_number = price_data_n.size

# TF graph input
X = tf.placeholder("float")
Y = tf.placeholder("float")

# Create a model

# Set model weights
W = tf.Variable(numpy.random.randn(), name="weight")
b = tf.Variable(numpy.random.randn(), name="bias")

# Set parameters
learning_rate = 0.1
training_iteration = 200

# Construct a linear model
model = tf.add(tf.matmul(X, W), b)

# Minimize squared errors
cost_function = tf.reduce_sum(tf.pow(model - Y, 2))/(2 * samples_number) #L2 loss
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost_function) #Gradient descent

# Initialize variables
init = tf.initialize_all_variables()

# Launch a graph
with tf.Session() as sess:
    sess.run(init)

    display_step = 20
    # Fit all training data
    for iteration in range(training_iteration):
        for (x, y) in zip(size_data_n, price_data_n):
            sess.run(optimizer, feed_dict={X: x, Y: y})

        # Display logs per iteration step
        if iteration % display_step == 0:
            print ("Iteration:", '%04d' % (iteration + 1), "cost=", "{:.9f}".format(sess.run(cost_function, feed_dict={X:size_data_n, Y:price_data_n})),\
            "W=", sess.run(W), "b=", sess.run(b))

    tuning_cost = sess.run(cost_function, feed_dict={X: normalize(size_data_n), Y: normalize(price_data_n)})

    print ("Tuning completed:", "cost=", "{:.9f}".format(tuning_cost), "W=", sess.run(W), "b=", sess.run(b))

    # Validate a tuning model

    testing_cost = sess.run(cost_function, feed_dict={X: size_data_test_n, Y: price_data_test_n})

    print ("Testing data cost:" , testing_cost)

    # Display a plot
    plt.figure()
    plt.plot(size_data_n, price_data_n, 'ro', label='Normalized samples')
    plt.plot(size_data_test_n, price_data_test_n, 'go', label='Normalized testing samples')
    plt.plot(size_data_n, sess.run(W) * size_data_n + sess.run(b), label='Fitted line')
    plt.legend()

    plt.show()