def newton_raph(fx, theta_0, thresh, x_data):
    '''
        Function to implement the Newton Raphson method
    '''

    #get the first and second derivative
    d1 = sp.diff(fx, theta)
    d2 = sp.diff(d1, theta)

    print('\nLatex Equations')
    print('Original Function:  ', sp.latex(fx))
    print('First Derivative:   ', sp.latex(d1))
    print('Second Derivative:  ',sp.latex(d2))

    #initialize the first derivative
    d_sub = d1.subs(theta, theta_0)
    d1_lam = sp.lambdify( x, d_sub, "numpy")
    d_1 = sum((d1_lam(x_data)))

    #Iterate using the Newton Raphson method while theta is < thresh
    n_iterations = 0    #Number of iterations to complete

    while abs(d_1) > thresh:
        n_iterations +=1
        d1_sub = d1.subs(theta, theta_0)
        d1_lam = sp.lambdify( x, d1_sub, "numpy")
        d_1 = np.sum((d1_lam(x_data)))

        d2_sub = d2.subs(theta, theta_0)
        d2_lam = sp.lambdify( x, d2_sub, "numpy")
        d_2 = np.sum((d2_lam(x_data)))

        #assign new value to theta for next iteration
        theta_0 = theta_0 - d_1/d_2

    theta_hat = theta_0
    print('\nNumber of Iterations: ', n_iterations)
    print('theta hat: ', theta_hat)
    return theta_hat

if __name__ == "__main__":
    import sympy as sp
    from sympy.abc import i
    import numpy as np
    import pandas as pd

    #Sample 1
    #create synthetic data
    n=10
    j=np.arange(1,n+1,1)
    x_data = -3.1 + 6*j/n

    #declare the sympy function
    theta = sp.Symbol('theta')
    x = sp.Symbol('x')

    fx = 1551*theta - sp.Sum( (sp.exp(theta*x) ),(i, 0, 9))
    theta_init = 3    #initial guess of theta
    thresh = 1e-10    #threshold for theta

    #Sample 2 (work in progress)
# =============================================================================
#     data = pd.read_csv(r'data/test_data.csv')
#     x_data = data['x']
#     y_data = data['y']
#
#     theta = sp.Symbol('theta')
#     x = sp.Symbol('x')
#     y = sp.symbol('y')
#
#     fx = 1/2 * sp.sum( (y-sp.exp(theta*x))**2),(i, 0, len(x_data-1)))
# =============================================================================

    #run function
    newton_raph(fx=fx, theta_0=theta_init, thresh=thresh, x_data = x_data)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Jul  2 13:54:12 2020

Function for time series novelty score calculation.
"""

import numpy as np

def _create_kernel(edge, sigma=1.0, mu=0.0):
    """
    Create a (2*edge+1) x (2*edge+1) gaussian kernel.

    Parameters
    ----------
    edge : int, optional
        Gaussian kernel window length / 2.
    sigma : float, optional
        Variance for the gaussian kernel construction. The default is 1.0.
    mu : float, optional
        Mean for the gaussian kernel construction. The default is 0.0.

    Returns
    -------
    kernel : numpy ndarray
        2D gaussian convolution kernel.

    """
    assert isinstance(edge,int), "Edge is not an integer."
    assert edge > 0, "Edge should be positive, non zero integer."

    grid = np.linspace(-1, 1, 2*edge + 1)
    x,y = np.meshgrid(grid,grid)
    d = np.sqrt(x**2 + y**2)
    gaussian_mat = np.exp(-((d - mu)**2 / (2.0 * sigma**2)))

    kernel_grid = np.sign(np.linspace(-edge, edge, 2*edge +1))
    signs = np.outer(kernel_grid, kernel_grid)
    signed_gaussian = signs * gaussian_mat
    kernel = signed_gaussian / np.sum(np.abs(signed_gaussian))

    return kernel

def compute_novelty(simmat, edge = 7, sigma=1.0, mu = 0.0):
    """
    Compute novelty score using the self similarity matrix and gaussian
    checkerboard convolution kernel, calculating the convolution along the
    self similarity matrix diagonal.

    Parameters
    ----------
    simmat : numpy ndarray
        N x N self similarity matrix.
    edge : float, optional
        Gaussian kernel window length / 2. The default is 7.
    sigma : float, optional
        Variance for the gaussian kernel construction. The default is 1.0.
    mu : float, optional
        Mean for the gaussian kernel construction. The default is 0.0.

    Returns
    -------
    nov : numpy ndarray
        1D novelty score vector.
    kernel : numpy ndarray
        2D gaussian convolution kernel.

    """

    assert isinstance(simmat,np.ndarray), "Self similarity matrix is not a numpy array."
    assert np.ndim(simmat) == 2, "Self similarity matrix is not 2-dimensional."
    assert simmat.shape[0] == simmat.shape[1], "Self similarity matrix is not square."
    assert 2*edge + 1 <= simmat.shape[0], "Kernel size is larger than the self similarity matrix."

    kernel = _create_kernel(edge, sigma, mu)

    N = simmat.shape[0]
    M = 2*edge + 1

    novelty = np.zeros(N)

    simmat_padded  = np.pad(simmat,edge,mode='constant')

    for i in range(N):
        novelty[i] = np.sum(simmat_padded[i:i+M, i:i+M] * kernel)

    return novelty, kernel


import numpy as np
from numpy import exp, sqrt, cos, pi, sin

from base_function import BaseFunction

class Levi(BaseFunction):
    target_E = 0.
    xmin = np.array([-10.,-10.])
    xmax = np.array([10.,10.])

    def getEnergy(self, coords):
        x, y = coords
        E = sin(3.*pi*x)**2 + (x-1.)**2 * (1. + sin(3*pi*y)**2) \
            + (y-1.)**2 * (1. + sin(2*pi*y)**2)
        return E

    def getEnergyGradient(self, coords):
        x, y = coords
        E = self.getEnergy(coords)

        dEdx = 2.*3.*pi* cos(3.*pi*x) * sin(3.*pi*x) + 2.*(x-1.) * (1. + sin(3*pi*y)**2)

        dEdy = (x-1.)**2 * 2.*3.*pi* cos(3.*pi*y) * sin(3.*pi*y) + 2. *  (y-1.) * (1. + sin(2*pi*y)**2) \
            + (y-1.)**2 * 2.*2.*pi * cos(2.*pi*y) * sin(2.*pi*y)

        return E, np.array([dEdx, dEdy])

if __name__ == "__main__":
    f = Levi()
    f.test_potential(f.get_random_configuration())

    from base_function import makeplot2d
    makeplot2d(f)


import numpy as np
import numpy.linalg as LA

def proj(v1,v2):
  '''
  Function to compute projection of v2 on v1
  '''
  alpha = np.dot(v1,v2)/np.dot(v1,v1)                                                  # proj(a,b) = aTb/aTa
  return alpha

def Gram_Schmid(M):
  '''
  Given any (m x n) matrix,M this function computers corresponding orthonormal matrix B such that,
  1. BTB = I
  2. C(M) = C(B) i.e Columnspace will be same
  '''
  row,col = M.shape
  Q = []                                                                               # List to append all orthogonal column vector later to be normalized
  for c in range(col):
    column_vector = M[:,c]
    if c == 0:                                                                         # First column vector is itself taken to be orthogonal hence append
      Q.append(column_vector)
    else:
      for ortho_col_vec in Q:                                                          # For all previous orthogonal column vectors do operation on current column vector
        temp_col_vec = column_vector.copy()
        column_vector -= np.multiply(proj(ortho_col_vec,temp_col_vec),ortho_col_vec)   # B = B - proj(A,B)*A for all A in Q
      if (LA.norm(column_vector)!=0):                                                  # If column vector is linearly independent then add to Q
        Q.append(column_vector)
  # Orthonormalizartion of Q
  B = []                                                                               # Output Orthonormalized Matrix
  for col_vec in Q:
    B.append(col_vec/LA.norm(col_vec))
  return np.array(B).T

A = np.array([[1,1],
              [1,0],
              [1,2]],dtype=np.float32)

B = Gram_Schmid(A)
print(B)
print(np.matmul(B.T,B))


import numpy as np

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

a, b, c = np.split(x, (3,6))

print("a = ",a)
print("b = ",b)
print("c = ",c)

##

k = np.arange(16).reshape(4,4)

print("k = ",k)

upperar, lowerar = np.vsplit(k, [2]) #vsplit stands for vertical split

print("upperar = ",upperar)
print("lowerar = ",lowerar)

upperar2, lowerar2 = np.hsplit(k, [2]) #hsplit stands for horizontal split

print("upperar2 = ",upperar2)
print("lowerar2 = ",lowerar2)

##

l = np.random.normal(10, 2, (3,3))

print("l = ",l)

print(np.sort(l, axis=0)) #sorted column-wise
print(np.sort(l, axis=1)) #sorted row-wise

##

t = np.arange(10, 20)

print("t = ",t)
print("t[0::2] = ",t[0::2])
print("t[1::3] = ",t[1::3])

##

d = np.random.randint(-1, 10, (5,5))

print("d = ", d)

print(d[:, 0]) #all row are selected, whereas only the first column is selected
print(d[:, 3]) #same here, just another column is selected
print(d[2, :])
print(d[0:3, 0:2])

##

f = np.random.randint(0, 10, size= (5, 5))

print("f = ",f)

copy = f[0:1, 0:3].copy()
#copy function provides to not changing the original values of the main array (which is f here)
print("copy = ",copy)
print("f = ",f)

##

#fancy index

g = np.arange(15, 0, -2)
print("g = ", g)

bring = [2, 5, 3]
print("g[bring] = ",g[bring])

##

h = np.arange(16).reshape(4,4)
print("h = ", h)

row = np.array([2, 1])
column = np.array([2, 2])

print("h[row, column] =", h[row, column])
print("h[2, [1, 2]] =", h[2, [1, 2]])
print("h[0:, h[1, 2]] =" , h[0:, [1, 2]])

##

j = np.arange(10)
print(j)

print(j<6)
print(j[j!=4])

##

# 4*x1 + x2 = 15
# 6*x1 + 2*x2 = 8   solve the two equations

q = np.array([[4, 1], [6, 2]])
w = np.array([15, 8])

print(np.linalg.solve(q, w))


'''
Computing percentiles
100xp
In this exercise, you will compute the percentiles of petal length of Iris versicolor.

Instructions
-Create percentiles, a NumPy array of percentiles you want to compute. These are the 2.5th,
25th, 50th, 75th, and 97.5th. You can do so by creating a list containing these ints/floats
and convert the list to a NumPy array using np.array(). For example, np.array([30, 50]) would
create an array consisting of the 30th and 50th percentiles.
-Use np.percentile() to compute the percentiles of the petal lengths from the Iris versicolor
samples. The variable versicolor_petal_length is in your namespace.
-Print the percentiles.
'''

import numpy as np
import seaborn as sns

versicolor_petal_length = np.array([4.7,  4.5,  4.9,  4.,  4.6,  4.5,  4.7,  3.3,  4.6,  3.9,  3.5,
                                    4.2,  4.,  4.7,  3.6,  4.4,  4.5,  4.1,  4.5,  3.9,  4.8,  4.,
                                    4.9,  4.7,  4.3,  4.4,  4.8,  5.,  4.5,  3.5,  3.8,  3.7,  3.9,
                                    5.1,  4.5,  4.5,  4.7,  4.4,  4.1,  4.,  4.4,  4.6,  4.,  3.3,
                                    4.2,  4.2,  4.2,  4.3,  3.,  4.1])

# Specify array of percentiles: percentiles
percentiles = np.array([2.5, 25, 50, 75, 97.5])

# Compute percentiles: ptiles_vers
ptiles_vers = np.percentile(versicolor_petal_length, percentiles)

# Print the result
print(ptiles_vers)


# MIT License
#
# Copyright (c) 2021 Playtika Ltd.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import numpy as np

def cohens_d(mu_1, mu_2, std):
    """

    Compute the standardized effect size as difference between the two means divided by the standard deviation.

    Parameters
    ----------
    mu_1 : float
        Mean of the first sample.
    mu_2 : float
        Mean of the second sample.
    std : float > 0
        Pooled standard deviation. It assumes that the variance of each population is the same.

    Returns
    -------
    effect_size : float
        Effect size as cohen's d coefficient
    """

    return (mu_1 - mu_2) / std

def cohens_h(p1, p2):
    """

    Compute the effect size as measure of distance between two proportions or probabilities. It is the difference
    between their arcsine transformations

    Parameters
    ----------
    p1 : float in interval (0,1)
        Proportion or probability of the first sample.
    p2 : float in interval (0,1)
        Proportion or probability of the second sample.

    Returns
    -------
    effect_size : float
        Effect size as cohen's h coefficient
    """

    return abs(2 * np.arcsin(np.sqrt(p1)) - 2 * np.arcsin(np.sqrt(p2)))

def pooled_std(sample1, sample2):
    """

    Compute pooled standard deviation between two samples.

    Parameters
    ----------
    sample1 : array_like
        Observation of first sample
    sample2 : array_like
        Observation of second sample

    Returns
    -------
    pooled_std : float > 0
        p-value for the test
    """
    # Compute the size of samples
    n1, n2 = len(sample1), len(sample2)

    # Compute the variance of the samples
    std1, std2 = np.var(sample1, ddof=1), np.var(sample2, ddof=1)

    # Compute the pooled standard deviation
    return np.sqrt(((n1 - 1) * std1 + (n2 - 1) * std2) / (n1 + n2 - 2))


import numpy as np
import csv

# =============================================================================
# Models
# =============================================================================

def least_squares_GD(y, tx, initial_w,
                     max_iters, gamma, verbose=False):
    """Least squares with MSE loss and Gradient Descent."""
    ws = [initial_w]
    w = initial_w
    losses = []

    for n_iter in range(max_iters):
        # Compute gradient and loss
        g = mse_grad(y, tx, w)
        loss = mse(y, tx, w)
        # Update the weights
        w = w - gamma * g
        w = w.ravel()
        # Store the weights and loss
        ws.append(w)
        losses.append(loss)

        if verbose:
            print("Gradient descent({bi}/{ti}): loss={l}".format(
                    bi=n_iter, ti=max_iters - 1, l=loss))

    return ws[-1], losses[-1]

def least_squares_SGD(y, tx, initial_w,
                      max_iters, gamma, verbose=False):
    """Least squares with MSE loss and Stochasitc Gradient Descent."""
    ws = [initial_w]
    losses = []
    w = initial_w
    for n_iter in range(max_iters):
        for minibatch_y, minibatch_tx in batch_iter(y, tx, batch_size=1):
            # Compute gradient and loss
            g = mse_grad(minibatch_y, minibatch_tx, w)
            loss = mse(minibatch_y, minibatch_tx, w)
            # Update the weights
            w = w - gamma * g
            w = w.ravel()
            # store w and loss
            ws.append(w)
            losses.append(loss)
            if verbose:
                print("Stochastic gradient descent({bi}/{ti}): loss={l}".format(
                    bi=n_iter, ti=max_iters - 1, l=loss))

    return ws[-1], mse(y, tx, ws[-1])

def least_squares(y, tx):
    """Linear regression fit using normal equations."""
    a = tx.T @ tx
    b = tx.T @ y
    w = np.linalg.solve(a, b)
    loss = mse(y, tx, w)
    return w, loss

def ridge_regression(y, tx, lambda_):
    """ Ridge regression fit using normal equations """
    a = (tx.T @ tx) + lambda_*2*tx.shape[0] * np.eye(tx.shape[1])
    b = tx.T @ y
    w = np.linalg.solve(a, b)
    return w, mse(y, tx, w)

def logistic_regression(y, tx, initial_w, max_iters,
                        gamma, verbose=False):
    """Logistic regression with log loss and Gradient Descent."""
    ws = [initial_w]
    w = initial_w
    losses = []

    for n_iter in range(max_iters):
        # Compute gradient and loss
        g = logistic_grad(y, tx, w)
        loss = logistic_error(y, tx, w)
        # Update the weights
        w = w - gamma * g
        w = w.ravel()
        # Store the weights and loss
        ws.append(w)
        losses.append(loss)

        if verbose:
            print("Gradient descent({bi}/{ti}): loss={l}".format(
                    bi=n_iter, ti=max_iters - 1, l=loss))

    return ws[-1], losses[-1]

def reg_logistic_regression(y, tx, lambda_, reg, initial_w,
                            max_iters, gamma, verbose=False,
                            early_stopping=True, tol = 0.0001,
                            patience = 5):
    """Regularized logistic regression with log loss and Gradient Descent with early stopping"""
    ws = [initial_w]
    w = initial_w
    losses = []

    for n_iter in range(max_iters):
        # Compute gradient and loss
        g = reg_logistic_grad(y, tx, w, lambda_, reg)
        loss = reg_logistic_error(y, tx, w, lambda_, reg)

        # Update the weights
        w = w - gamma * g
        w = w.ravel()
        # Store the weights and loss
        ws.append(w)
        losses.append(loss)

        if verbose:
            print("Gradient descent({bi}/{ti}): loss={l}".format(
                    bi=n_iter, ti=max_iters - 1, l=loss))

        # Early stopping
        if (early_stopping) and (n_iter > patience):
            # Check if loss has improved by tol in last patience iters
            l_pat = reg_logistic_error(y, tx, ws[-patience], lambda_, reg)
            l_1 = reg_logistic_error(y, tx, ws[-1], lambda_, reg)
            if ((l_pat - l_1) < tol):
                print(f"Stopped after {n_iter} it.")
                break

    return ws[-1], losses[-1]

# =============================================================================
# Cost functions
# =============================================================================

def mse(y, tx, w):
    """Mean squared error loss function."""
    e = y - tx @ w
    return (1/(2*tx.shape[0])) * np.sum(e**2)

def logistic_error(y, tx, w):
    """Log loss function."""
    a = sigmoid(tx @ w)
    loss = - (1 / tx.shape[0]) * np.sum((y * np.log(a)) + ((1 - y) * np.log(1 - a)))
    return loss

def reg_logistic_error(y, tx, w, lambda_, reg):
    """Log loss function with regularization term."""
    assert (reg==1 or reg==2), "reg needs to be 1 or 2"
    loss = logistic_error(y, tx, w) + lambda_ * (np.linalg.norm(w, reg) ** reg)
    return loss

# =============================================================================
# Gradients
# =============================================================================

def mse_grad(y, tx, w):
    """Compute gradient for MSE loss."""
    e = y - tx @ w
    return (-1/tx.shape[0]) * tx.T @ e

def logistic_grad(y, tx, w):
    """Compute gradient for log loss."""
    e = sigmoid(tx @ w) - y
    return (1/tx.shape[0]) * tx.T @ e

def reg_logistic_grad(y, tx, w, lambda_, reg):
    """Compute gradient for log loss with regularization."""
    assert (reg==1 or reg==2), "reg needs to be 1 or 2"
    if (reg==1):
        # L1 regularization
        return logistic_grad(y, tx, w) + lambda_ * np.sign(w)
    else:
        # L2 regularization
        return logistic_grad(y, tx, w) + 2 * lambda_ * w

# =============================================================================
# Activation functions
# =============================================================================

def sigmoid(x):
    """Compute sigmoid function."""
    epsilon = 1E-12
    a = 1 / (1 + np.exp(-x))
    a = np.where(np.isclose(a, 0.0), epsilon, a)
    a = np.where(np.isclose(a, 1.0), (1-epsilon), a)
    return a

# =============================================================================
# Helpers
# =============================================================================

def batch_iter(y, tx, batch_size, num_batches=1, shuffle=True):
    # Please note this code was provided to us during the lab sessions.
    """
    Generate a minibatch iterator for a dataset.
    Takes as input two iterables (here the output desired values 'y' and the input data 'tx')
    Outputs an iterator which gives mini-batches of `batch_size` matching elements from `y` and `tx`.
    Data can be randomly shuffled to avoid ordering in the original data messing with the randomness of the minibatches.
    Example of use :
    for minibatch_y, minibatch_tx in batch_iter(y, tx, 32):
        <DO-SOMETHING>
    """
    data_size = len(y)

    if shuffle:
        shuffle_indices = np.random.permutation(np.arange(data_size))
        shuffled_y = y[shuffle_indices]
        shuffled_tx = tx[shuffle_indices]
    else:
        shuffled_y = y
        shuffled_tx = tx
    for batch_num in range(num_batches):
        start_index = batch_num * batch_size
        end_index = min((batch_num + 1) * batch_size, data_size)
        if start_index != end_index:
            yield shuffled_y[start_index:end_index], shuffled_tx[start_index:end_index]

def import_data(path="data/"):
    """
    Import csv files and return array of X,y and vector of the column names.
    """
    train = np.loadtxt(
        f"{path}train.csv",
        delimiter = ",",
        skiprows=0,
        dtype=str
    )

    test = np.loadtxt(
        f"{path}test.csv",
        delimiter = ",",
        skiprows=0,
        dtype=str
    )

    col_names = train[0,:]

    # Remove column names
    train = np.delete(train, obj=0, axis=0)
    test = np.delete(test, obj=0, axis=0)

    # Map 0 & 1 to label
    label_idx = np.where(col_names == "Prediction")[0][0]
    train[:,label_idx] = np.where(train[:,label_idx]=="s", 1, 0)

    test[:,label_idx] = 0

    # Replace -999 with nan
    train = train.astype(np.float32)
    train[train == -999] = np.nan

    test = test.astype(np.float32)
    test[test == -999] = np.nan
    return train, test, col_names

def create_csv_submission(ids, y_pred, name):
    # Please note this code was provided to us during the lab sessions.
    """
    Creates an output file in csv format for submission to AIcrowd
    Arguments: ids (event ids associated with each prediction)
               y_pred (predicted class labels)
               name (string name of .csv output file to be created)
    """
    with open(name, 'w') as csvfile:
        fieldnames = ['Id', 'Prediction']
        writer = csv.DictWriter(csvfile, delimiter=",", fieldnames=fieldnames)
        writer.writeheader()
        for r1, r2 in zip(ids, y_pred):
            writer.writerow({'Id':int(r1),'Prediction':int(r2)})

def standardize_numpy(x, mean=None, std=None):
    """Standardize the original data set. Works on numpy arrays."""
    if mean is None: mean = x.mean(axis=0, keepdims=True)
    x = x - mean
    if std is None: std = x.std(axis=0, keepdims=True)
    x = x / std
    return x, mean, std

# =============================================================================
# Prepare features
# =============================================================================

def split_X_y(train, test, cols):
    """Create tx matrix for train & test + y vector for train."""
    idx_id = np.where(cols=="Id")[0][0]
    idx_pred = np.where(cols=="Prediction")[0][0]

    tx_train = np.delete(train, [idx_id, idx_pred], axis=1)
    y_train = train[:,idx_pred].copy()
    tx_test = np.delete(test, [idx_id, idx_pred], axis=1)

    return tx_train, y_train, tx_test

def build_poly(x, degree):
    """Polynomial basis functions for each column of x, for j=1 up to j=degree, and single constant term."""
    if (degree < 0): raise ValueError("degree must be positive")

    phi = np.empty((x.shape[0], x.shape[1]*degree+1))

    # Constant term
    phi[:,-1] = 1

    # Higher order terms
    for j in range(x.shape[1]):
        phi[:,j*degree] = x[:,j]
        for d in range(1,degree):
            col = j*degree+d
            phi[:,col] = phi[:,col-1] * x[:,j]

    return phi

def prepare_features(tx_nan, degree, mean_nan=None, mean=None, std=None):
    """Clean and prepare for learning.  Mean imputing, missing value indicator, standardize."""
    # Get column means, if necessary
    if mean_nan is None: mean_nan = np.nanmean(tx_nan,axis=0)

    # Replace NaNs
    tx_val = np.where(np.isnan(tx_nan), mean_nan, tx_nan)

    # Polynomial features
    tx = build_poly(tx_val, degree)
    const_col = tx.shape[1]-1

    # Add NaN indicator columns
    nan_cols = np.flatnonzero(np.any(np.isnan(tx_nan), axis=0))

    ind_cols = np.empty((tx_nan.shape[0], nan_cols.shape[0]))
    ind_cols = np.where(np.isnan(tx_nan[:,nan_cols]), 1, 0)

    tx = np.c_[tx, ind_cols]

    # Standardize
    tx, mean, std = standardize_numpy(tx, mean, std)
    tx[:,const_col] = 1.0

    return tx, mean, std, mean_nan, nan_cols

# =============================================================================
# Performance metrics
# =============================================================================

def logistic_prediction(tx, w):
    """Make a prediction with logistic regression model."""
    return np.rint(sigmoid(tx @ w))

def regression_prediction(tx, w):
    """Make a prediction with linear regression model."""
    return tx @ w

def f1_score(y_targ, y_pred):
    """Compute the F1 score of a prediction."""
    mask_targ = (y_targ == 1)
    mask_pred = (y_pred == 1)

    # Total positives
    total_pred = np.count_nonzero(mask_pred)
    total_targ = np.count_nonzero(mask_targ)

    # True positives
    true_pos = np.count_nonzero(mask_pred[mask_targ])

    if (true_pos == 0) or (total_pred == 0) or (total_targ == 0):
        return 0.0

    precision = true_pos / total_pred
    recall = true_pos / total_targ

    # Compute F1 score
    score = 2*(precision*recall)/(precision+recall)

    return score

def accuracy(y_targ, y_pred):
    """Compute the accuracy of a prediction"""
    total_wrong = np.count_nonzero(y_targ-y_pred)
    return 1.0 - (total_wrong / len(y_pred))


import numpy as np
import scipy.signal as sig
import math
import cv2
from advanced_fields.computer_vision.utils import scale_0_255

img = np.array([[0, 90, 0],
                [105, 0, 55],
                [0, 40, 0]])

# img = cv2.imread('../../../datasets/per_field/cv/color_lady.jpg', cv2.IMREAD_GRAYSCALE)

# Note:
# Convolution reverses the direction of one of the functions it works on.
# one function is parameterized with τ and the other with -τ.
#   reference: https://en.wikipedia.org/wiki/Convolution#Definition
# the desired kernel must be flipped (K[::-1,::-1])) the in both axes to get the expected result.
# how it's done:
# desired_kernel = [[0, 1, 2],
#                   [3, 4, 5],
#                   [6, 7, 8]]
# used_kernel = np.array(desired_kernel)[::-1,::-1]
# used_kernel = np.flip(desired_kernel, axis=(0,1))

kernel_x = np.array([[-1, 0, 1]])[::-1, ::-1]
# kernel_x = np.flip([[-1, 0, 1]], axis=1)
kernel_y = np.array([[1], [0], [-1]])[::-1, ::-1]
# kernel_y = np.flip([[1], [0], [-1]], axis=0)

GV_x = sig.convolve2d(img, kernel_x, mode='valid')[1, 0]       # returns [[0], [-50], [0]]
GV_y = sig.convolve2d(img, kernel_y, mode='valid')[0, 1]       # returns [[0, 50, 0]]

# Gradient Vector:
GV = np.array([[GV_x], [GV_y]])
print(f'Gradient Vector: \n {GV}')

# Gradient Vector's Magnitude:
# GV_M = math.sqrt(GV_x ** 2 + GV_y ** 2)
GV_M = np.sqrt(GV_x ** 2 + GV_y ** 2)
print(f"Gradient Vector's Magnitude: {GV_M}")

# Gradient Vector's Direction (\ angle):
# GV_theta = math.degrees(math.atan(GV_y / GV_x))
# GV_theta = np.arctan(GV_y / GV_x) * 180 / np.pi
GV_theta = np.degrees(np.arctan(GV_y / GV_x))
print(f"Gradient Vector's Direction: {GV_theta}°")

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

# cv2 implementation
img = cv2.imread('../../../datasets/per_field/cv/color_man_2004.jpg')
# img = np.float32(img) / 255.0  # scaling

GV_x_sobel = scale_0_255(cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1))  # cv2.CV_8U, ksize=5
GV_y_sobel = scale_0_255(cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1))  # cv2.CV_8U, ksize=5

GV_M, GV_theta = cv2.cartToPolar(GV_x_sobel, GV_y_sobel, angleInDegrees=True)


## 2. Calculating differences ##

female_diff = (10771 - 16280.5) / 16280.5
male_diff = (21790 - 16280.5) / 16280.5

## 3. Updating the formula ##

female_diff = (10771 - 16280.5) ** 2 / 16280.5
male_diff = (21790 - 16280.5) ** 2 / 16280.5
gender_chisq = female_diff + male_diff

## 4. Generating a distribution ##

chi_squared_values = []
from numpy.random import random
import matplotlib.pyplot as plt

for i in range(1000):
    sequence = random((32561,))
    sequence[sequence < .5] = 0
    sequence[sequence >= .5] = 1
    male_count = len(sequence[sequence == 0])
    female_count = len(sequence[sequence == 1])
    male_diff = (male_count - 16280.5) ** 2 / 16280.5
    female_diff = (female_count - 16280.5) ** 2 / 16280.5
    chi_squared = male_diff + female_diff
    chi_squared_values.append(chi_squared)

plt.hist(chi_squared_values)

## 6. Smaller samples ##

female_diff = (107.71 - 162.805) ** 2 / 162.805
male_diff = (217.90 - 162.805) ** 2 / 162.805
gender_chisq = female_diff + male_diff

## 7. Sampling distribution equality ##

chi_squared_values = []
from numpy.random import random
import matplotlib.pyplot as plt

for i in range(1000):
    sequence = random((300,))
    sequence[sequence < .5] = 0
    sequence[sequence >= .5] = 1
    male_count = len(sequence[sequence == 0])
    female_count = len(sequence[sequence == 1])
    male_diff = (male_count - 150) ** 2 / 150
    female_diff = (female_count - 150) ** 2 / 150
    chi_squared = male_diff + female_diff
    chi_squared_values.append(chi_squared)

plt.hist(chi_squared_values)

## 9. Increasing degrees of freedom ##

diffs = []
observed = [27816, 3124, 1039, 311, 271]
expected = [26146.5, 3939.9, 944.3, 260.5, 1269.8]

for i, obs in enumerate(observed):
    exp = expected[i]
    diff = (obs - exp) ** 2 / exp
    diffs.append(diff)

race_chisq = sum(diffs)

## 10. Using SciPy ##

from scipy.stats import chisquare
import numpy as np
observed = np.array([27816, 3124, 1039, 311, 271])
expected = np.array([26146.5, 3939.9, 944.3, 260.5, 1269.8])

chisquare_value, race_pvalue = chisquare(observed, expected)

Data Analyst in Python/Step 5 - Probability and Statistics/5. Hypothesis Testing Fundamentals/2. Chi-squared tests.py


"""
Similar to the file of adjoint_univariate_nonlinear_system.py but here we use
the Automatic Differentiation of Jax to obtain the gradients of the nonlinear
function.
"""

import time

import jax.numpy as jnp
import jax.scipy as jsp
from jax import grad

from scipy import optimize

def residuum(displacement, force, theta):
    r"""
                    /           b              \
     -F + k*(a - v)*|- -------------------- + 1|
                    |     _________________    |
                    |    /          2    2     |
                    \  \/  2*a*v + b  - v      /
    """
    vertical_distance, horizontal_distance, spring_stiffness = theta
    residual_value = (
        spring_stiffness * (vertical_distance - displacement) * (
            1
            -
            (
                horizontal_distance
            ) / (
                jnp.sqrt(horizontal_distance**2 + 2*vertical_distance*displacement - displacement**2)
            )
        )
        -
        force
    )

    return residual_value

if __name__ == "__main__":
    a_true = 1.0
    b_true = 1.0
    k_true = 200.0

    force_value = 10.0

    v_ref = optimize.newton(
        func=residuum,
        fprime=grad(residuum),
        args=(force_value, (a_true, b_true, k_true)),
        x0=1.0
    )

    # print(v_ref)

    ######
    # Solving the Forward Problem
    ######

    theta_guess = jnp.array([0.9, 0.9, 180.0])
    additional_args = (force_value, theta_guess)

    v = optimize.newton(
        func=residuum,
        fprime=grad(residuum),
        args=additional_args,
        x0=1.0,
    )

    # The "J" loss function is the least-squares (quadratic loss)
    def loss_function(v, theta):
        return 0.5 * (v - v_ref)**2

    J = loss_function(v, theta_guess)

    ##### Adjoint Method

    time_adjoint = time.time_ns()

    current_args_adjoint = (v, force_value, theta_guess)

    del_J__del_theta = grad(loss_function, argnums=1)(v, theta_guess).reshape((1, -1))
    del_J__del_x = grad(loss_function, argnums=0)(v, theta_guess)
    del_f__del_theta = grad(residuum, argnums=2)(*current_args_adjoint).reshape((1, -1))
    del_f__del_x = grad(residuum, argnums=0)(*current_args_adjoint)

    # print(del_J__del_theta)
    # print(del_J__del_x)
    # print(del_f__del_theta)
    # print(del_f__del_x)

    adjoint_variable = -1.0 / del_f__del_x * del_J__del_x

    d_J__d_theta_adjoint = del_J__del_theta + adjoint_variable * del_f__del_theta

    time_adjoint = time.time_ns() - time_adjoint

    print("Adjoint Sensitivities using Autodiff for gradients and Jacobians")
    print(d_J__d_theta_adjoint)
    print("Timing")
    print(time_adjoint)


"""
Computing pi with parallel quadrature formula
"""
import numpy
from mpi4py import MPI
import sys

comm = MPI.COMM_WORLD
mynode = comm.Get_rank()
totalnodes = comm.Get_size()

def fun(x):
    return (4.0/(1.0 + x*x))

truepi = 3.141592653589793238462643
dest = 0
pi = numpy.zeros(1)

# Initialize value of n only if this is rank 0
# we are not checking the input, if the user does something that
# has no meaning we are going to fail badly!
if mynode == 0:
    if len(sys.argv) == 1:
        n = numpy.full(1, 20, dtype=int) # default value
    else:
        n = numpy.full(1,int(sys.argv[1]),dtype=int)
else:
    n = numpy.zeros(1, dtype=int)

# Broadcast n to all processes
comm.Bcast(n, root=0)

# Compute local integral
my_pi = numpy.zeros(1)
h = 1.0/(n*totalnodes)
for i in numpy.arange(1+mynode*n,n*(mynode+1)+1):
    x = h*(i - 0.5)
    my_pi = my_pi + fun(x)
my_pi = h*my_pi

# Send partition back to root process:
comm.Reduce(my_pi, pi, MPI.SUM, dest)

# Only print the result in process 0
if mynode == 0:
    print('The Integral Sum =', pi[0]," The Error is ",numpy.abs(pi[0]-truepi))


"""
This module assumes that all geometrical points are
represented as 1D numpy arrays.

It was designed and tested on 2D points,
but if you try it on 3D points you may
be pleasantly surprised ;-)
"""
import numpy as np

def point_distance(x, y):
    """Returns euclidean distance between points x and y"""
    return np.linalg.norm(x-y)

def point_projected_on_line(line_s, line_e, point):
    """Project point on line that goes through line_s and line_e

    assumes line_e is not equal or close to line_s
    """
    line_along = line_e - line_s

    transformed_point = point - line_s

    point_dot_line  = np.dot(transformed_point, line_along)
    line_along_norm = np.dot(line_along, line_along)

    transformed_projection = (point_dot_line / line_along_norm) * line_along

    return transformed_projection + line_s

def point_segment_distance(segment_s, segment_e, point):
    """Returns distance from point to the closest point on segment
    connecting points segment_s and segment_e"""
    projected = point_projected_on_line(segment_s, segment_e, point)
    if np.isclose(point_distance(segment_s, projected) + point_distance(projected, segment_e),
        point_distance(segment_s, segment_e)):
        # projected on segment
        return point_distance(point, projected)
    else:
        return min(point_distance(point, segment_s), point_distance(point, segment_e))


import numpy as np
import pandas as pd

import matplotlib.pyplot as plt

from O5 import prepare_data_wine

# 1. Standardizing data

X_train, X_test, y_train, y_test, df_wine = prepare_data_wine(standardize=True,
                                                              split=True,
                                                              dataframe=True)

# 2. Constructing covariance matrix

# After completing the mandatory preprocessing by executing the preceding code,
# let's advance to the second step: constructing the covariance matrix.
# The symmetric d×d-dimensional covariance matrix, where d is the number of
# dimensions in the dataset, stores the pairwise covariances between the
# different features. For example, the covariance between two features, x(j)
# and x(k) , on the population level can be calculated via the following
# equation:
#       σ(jk) = (1 / (n - 1) ) * ∑(i=1, n) (x(j, i) - μ(j))(x(k, i) - μ(k))

# Here, μμ jj and μμ kk are the sample means of features j and k, respectively.
# Note that the sample means are zero if we standardized the dataset. A
# positive covariance between two features indicates that the features increase
# or decrease together, whereas a negative covariance indicates that the
# features vary in opposite directions. For example, the covariance matrix of
# three features can then be written as follows (note that Σ stands for the
# Greek uppercase letter sigma, which is not to be confused with summation
# symbol:
#       Σ = [σ(1)^2 σ(1,2) σ(1, 3);σ(2,1) σ(2)^2 σ(2, 3);σ(3,1) σ(3, 2) σ(3)^2]

# The eigenvectors of the covariance matrix represent the principal components
# (the directions of maximum variance), whereas the corresponding eigenvalues
# will define their magnitude. In the case of the Wine dataset, we would obtain
# 13 eigenvectors  and eigenvalues from the 13 × 13 -dimensional covariance
# matrix.

# Now, for our third step, let's obtain the eigenpairs of the covariance
# matrix. As you will remember from our introductory linear algebra classes, an
# eigenvector, v, satisfies the following condition:
#       Σv = λv
# Here, λ is a scalar: the eigenvalue.

cov_mat = np.cov(X_train.T)

# 3. Obtaining the eigenvalues and eigenvectors of the covariance matrix.

eigen_values, eigen_vectors = np.linalg.eig(cov_mat)
print(f"EigenValues \n{eigen_values}")

# The numpy.linalg.eig function was designed to operate on
# both symmetric and non-symmetric square matrices. However,
# you may find that it returns complex eigenvalues in certain
# cases.
# A related function, numpy.linalg.eigh , has been
# implemented to decompose Hermetian matrices, which is a
# numerically more stable approach to working with symmetric
# matrices such as the covariance matrix; numpy.linalg.eigh
# always returns real eigenvalues.

# Since we want to reduce the dimensionality of our dataset by compressing it
# onto a new feature subspace, we only select the subset of the eigenvectors
# (principal components) that contains most of the information (variance). The
# eigenvalues define the magnitude of the eigenvectors, so we have to sort the
# eigenvalues by decreasing magnitude; we are interested in the top k
# eigenvectors based on the values of their corresponding eigenvalues. But
# before we collect those k most informative eigenvectors, let's plot the
# variance explained ratios of the eigenvalues. The variance explained ratio of
# an eigenvalue, λ(j) , is simply the fraction of an eigenvalue, λ(j) and the
# total sum of eigenvalues:
#       Explained variance ratio = λ(j) / ∑(i=1, d)λ(i)

# Using the NumPy cumsum function, we can then calculate the cumulative sum of
# explained variances, which we will then plot via Matplotlib's step function:

total = sum(eigen_values)
explained_variance = [(i / total) for i in sorted(eigen_values, reverse=True)]
cumulative_sum_explained_variance = np.cumsum(explained_variance)

plt.bar(range(1, 14), explained_variance, alpha=0.5, align='center',
        label='Individual explained variance')

plt.step(range(1, 14), cumulative_sum_explained_variance, where='mid',
         label='Cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal component index')
plt.legend(loc='best')
plt.tight_layout()
plt.show()

# The resulting plot indicates that the first principal component alone
# accounts for approximately 40 percent of the variance.
# Also, we can see that the first two principal components combined explain
# almost 60 percent of the variance in the dataset
# we should remind ourselves that PCA is an unsupervised method, which means
# that information about the class labels is ignored. Whereas a random forest
# uses the class membership information to compute the node impurities,
# variance measures the spread of values along a feature axis.

# 4. Sorting the eigenvalues by decreasing order to rank the eigenvectors
eigen_pairs = [(np.abs(eigen_values[i]), eigen_vectors[:, i])
               for i in range(len(eigen_values))]

eigen_pairs = sorted(eigen_pairs, key=lambda x: x[0], reverse=True)

# 5. Select k(here k=2) eigenvectors, which correspond to the k largest
# eigenvalues, where k is the dimensionality of the new feature subspace
# (k <= d)

# Now, we collect the two eigenvectors that correspond to the two largest
# eigenvalues, to capture about 60 percent of the variance in this dataset.
# Note that two eigenvectors have been chosen for the purpose of illustration,
# since we are going to plot the data via a two-dimensional scatter plot later
# in this subsection. In practice, the number of principal components has to be
# determined by a tradeoff between computational efficiency and the performance
# of the classifier.
first_eigenvalue_vector = eigen_pairs[0][1]
second_eigenvalue_vector = eigen_pairs[1][1]

# 6. Construct a projection matrix, W, from the "top" k eigenvectors

w = np.hstack((first_eigenvalue_vector[:, np.newaxis],
               second_eigenvalue_vector[:, np.newaxis]))
print(w)

# 7. Transform the d-dimensional input dataset, X, using the projection matrix,
# W, to obtain the new k-dimensional feature subspace.
#       Transformation: x' = xW for x ∈ X
print(X_train[0].dot(w))

# Or full transformation: X' = XW

X_train_pcs = X_train.dot(w)
colors = ['red', 'blue', 'green']
markers = ['s', 'x', 'o']
for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_pcs[y_train == l, 0],
                X_train_pcs[y_train == l, 1],
                c=c, label=l, marker=m)

plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()
# As we can see in the resulting plot, the data is more spread along the x-axis
# —the first principal component—than the second principal component (y-axis),
# which is consistent with the explained variance ratio plot that we created in
# the previous subsection. However, we can tell that a linear classifier will
# likely be able to separate the classes well.
# Although we encoded the class label information for the purpose of
# illustration in the preceding scatter plot, we have to keep in mind that PCA
# is an unsupervised technique that doesn't use any class label information.


#!/usr/bin/env python3
# coding=utf-8

import matplotlib.pyplot as plt
import numpy as np

loc = 10.0
scale = 1.0

data = np.random.normal(loc, scale, size=6000)

# http://blog.csdn.net/lanchunhui/article/details/50163669
# 高斯分布（Gaussian Distribution）的概率密度函数（probability density function）
x_data = np.linspace(data.min(), data.max())
plt.plot(x_data, 1. / (np.sqrt(2 * np.pi) * scale) * np.exp(-(x_data - loc) ** 2 / (2 * scale ** 2)))

count, bins, _ = plt.hist(data, 30, normed=True)
# plt.plot(bins, 1. / (np.sqrt(2 * np.pi) * scale) * np.exp(-(bins - loc) ** 2 / (2 * scale ** 2)))

plt.show()

# Build a vector of 10000 normal deviates with variance 0.5^2 and mean 2
mu, sigma = 2, 0.5
v = np.random.normal(mu, sigma, 5000)
# Plot a normalized histogram with 50 bins
plt.hist(v, bins=50, normed=1)  # matplotlib version (plot)

# Compute the histogram with numpy and then plot it
(n, bins) = np.histogram(v, bins=50, normed=True)  # NumPy version (no plot)
plt.plot(.5 * (bins[1:] + bins[:-1]), n)

plt.grid(True)

plt.show()


import math
import random
import scipy.special
import scipy.optimize

PI    = 3.1415926535897932384626433832795
SQRT2 = 1.4142135623730950488016887242097

def Phi(z):
    """
    The cumulative function of the unit gaussian distribution
    """

    return  0.5 * (1.0 + scipy.special.erf(z / SQRT2))

def phi(z):
    """
    PDF of the standard gaussian distribution
    """

    return (1.0 / math.sqrt(2.0*PI)) * math.exp(-0.5 * z * z)

def SampleTG(mu, sigma, a, b):
    """
    Sample from
    """

    if sigma <= 0.0:
        raise ValueError("Sample: non-positive sigma")

    # for a moment, work with true bounded gaussian
    if math.isinf(a):
        raise ValueError("Sample: infinite a")
    if math.isinf(b):
        raise ValueError("Sample: infinite b")

    if a >= b:
        raise ValueError("Sample: a >= b")

    while True:
        r = random.gauss(mu, sigma)
        if r >= a and r <= b:
            return r

    return math.Inf

def Mean(mu, sigma, a, b):
    """
    Mean for truncated gaussian
    """

    if sigma <= 0.0:
        raise ValueError("Mean: non-positive sigma")

    # for a moment, work with true bounded gaussian
    if math.isinf(a):
        raise ValueError("Mean: infinite a")
    if math.isinf(b):
        raise ValueError("Mean: infinite b")

    if a >= b:
        raise ValueError("Mean: a >= b")

    alfa = (a - mu) / sigma
    beta = (b - mu) / sigma

    Z = Phi(beta) - Phi(alfa)

    return mu + sigma*(phi(alfa) - phi(beta))/Z

def f(mu, mean, sigma, a, b):
    """
    Function to search for true mu when particular mean is requested
    Root of this function would be right mu
    """
    return mean - Mean(mu, sigma, a, b)

if __name__ == "__main__":

    random.seed(12345)

    # some test printouts
    # print(phi(0.0))
    # print(Phi(0.0))

    a = 50000.0
    b = 250000.0
    mean = 70000.0
    sigma = 24000.0

    mu = scipy.optimize.brentq(f, a, b, args=(mean, sigma, a, b))
    print("Found mu = {0} for the desired mean {1} and sigma {2}".format(mu, mean, sigma))

    # test sampling

    N  = 100000
    s  = 0.0
    s2 = 0.0
    for k in range(0, N):
        q   = SampleTG(mu, sigma, a, b)
        if q < a:
            raise ValueError("Test: sampled value below a")
        if q > b:
            raise ValueError("Test: sampled value above b")
        s  += q
        s2 += q*q

    print("Sampled {0} truncated gaussians and got observed mean = {1}".format(N, s/float(N)))


"""
The prime factors of 13195 are 5, 7, 13 and 29.
What is the largest prime factor of the number 600851475143 ?

https://projecteuler.net/problem=3
"""

import numpy as np

# To determine if a number n is prime, we need to:
# 1. check whether n is evenly divisible by 2
# 2. check whether n is evenly divisible by one of the uneven numbers from 3 to sqrt(n) + 1
# 3. if neither 1 nor 2 apply, n is prime

# To find the largest prime factor of n, we repeatedly divide n by its smallest prime factor
# until we can't divide it further.

def find_smallest_prime_factor(number):
    upper_bound = int(np.sqrt(number)) + 1

    for i in range(2, upper_bound):
        if number % i == 0:
            return i

    return number

def find_largest_prime_factor(number):
    while True:
        smallest_factor = find_smallest_prime_factor(number)

        if smallest_factor < number:
            number //= smallest_factor
        else:
            return number

result = find_largest_prime_factor(600851475143)
print('result: ', result)


"""
Write a Python code that fulfills the following specification.
dataset: Female_Stats.csv

The Data Are From 214 Females In Statistics Classes At
The University Of California At Davis.

Column1 = Student’s Self-Reported Height,

Column2 = Student’s Guess At Her Mother’s Height, And

Column 3 = Student’s Guess At Her Father’s Height.

All Heights Are In Inches.

task01:
Build A Predictive Model And Conclude If Both Predictors
(Independent Variables) Are Significant For A Students’ Height Or Not?
(Use pvalue concepts).

task02:
When Father’s Height Is Held Constant,
The Average Student Height Increases
By How Many Inches For Each One-Inch Increase In Mother’s Height.

task03:
When Mother’s Height Is Held Constant,
The Average Student Height Increases
By How Many Inches For Each One-Inch Increase In Father’s Height.

"""

# Importing the libraries
import numpy as np
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Female_Stats.csv')

# Check data Types for each columns
print(dataset.dtypes)

# Seperate Features and Labels
features = dataset.iloc[:,1:].values
labels = dataset.iloc[:, [0]].values

# Check Column wise is any data is missing or NaN
dataset.isnull().any(axis=0)

from sklearn.model_selection import train_test_split
features_train, features_test, labels_train, labels_test = train_test_split(features, labels, test_size = 0.2, random_state = 0)

# Fitting Multiple Linear Regression to the Training set
# Whether we have Univariate or Multivariate, class is LinearRegression

from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
regressor.fit(features_train, labels_train)

Pred = regressor.predict(features_test)

print (pd.DataFrame(zip(np.round(Pred,2), labels_test)))

import statsmodels.api as sm

features_sm = sm.add_constant(features)
est = sm.OLS(labels, features_sm)
est2 = est.fit()

print (est2.summary())

"""
as both columns ( mom and dad are having p values less than 5%, both
                 heights are significant for student's height)
"""

"""

When Father’s Height Is Held Constant,
The Average Student Height Increases
By How Many Inches For Each One-Inch Increase In Mother’s Height.

"""

print (regressor.coef_[0][0])

"""

When Mother’s Height Is Held Constant,
The Average Student Height Increases
By How Many Inches For Each One-Inch Increase In Father’s Height.

"""
print (regressor.coef_[0][1])

# Version 2 of solution

import pandas as pd
import numpy as np

dataset=pd.read_csv("stats_females.csv")

features=dataset.iloc[:,1:]
labels=dataset.iloc[:,0]

from sklearn.model_selection import train_test_split
features_train,features_test,labels_train,labels_test=train_test_split(features,labels,test_size=0.2,random_state=0)

from sklearn.linear_model import LinearRegression
reg=LinearRegression()
reg.fit(features_train,labels_train)

import statsmodels.formula.api as sm
features=np.append(arr=np.ones((214,1)).astype(int),values=features,axis=1)

features_opt=features[:,[0,1,2]]
regressor_OLS=sm.OLS(labels,features_opt).fit()
regressor_OLS.summary()

"""
When Father’s Height Is Held Constant, The Average Student Height Increases
By How Many Inches For Each One-Inch Increase In Mother’s Height.
"""
print("When Father's Height is Held Constant then the average height increase by",regressor_OLS.params[1])

print("When Mother's Height is Held Constant then the average height increase by",regressor_OLS.params[2])


from scipy import signal as sig
import numpy as np
import matplotlib.pyplot as plt
import cv2

def gaussian_2d_kernel(ker_shape, sigm):
    """
    Creates gaussian kernel

    :param ker_shape: Used in defining kernel's shape
    :param sigm: sigma parameter for equation
    :return: Gaussian kernel with shape of `ker_shape`
    """
    ker = np.zeros(ker_shape)
    ind = np.arange(-np.floor(ker_shape[0] / 2), np.floor(ker_shape[1] / 2) + 1, 1)
    XX, YY = np.meshgrid(ind, ind)

    for row in range(ker_shape[0]):
        for col in range(ker_shape[1]):
            ker[row, col] = (1 / 2 * np.pi * sigm ** 2) * np.exp(
                (-1 * (XX[0, col] ** 2 + YY[row, 0] ** 2) / (2 * sigm ** 2)))

    return ker

def laplacian_of_gaussian_2d_kernel(ker_shape, sigm):
    """
    Creates laplacian of gaussian kernel

    :param ker_shape: Used in defining kernel's shape
    :param sigm: sigma parameter for equation
    :return: LoG kernel with shape of `ker_shape`
    """
    ker = np.zeros(ker_shape)
    ind = np.arange(-np.floor(ker_shape[0] / 2), np.floor(ker_shape[1] / 2) + 1, 1)
    XX, YY = np.meshgrid(ind, ind)

    for row in range(ker_shape[0]):
        for col in range(ker_shape[1]):
            ker[row, col] = (-1 / (np.pi * sigm ** 4)) * (1 - (XX[0, col] ** 2 + YY[row, 0] ** 2) / (2 * sigm ** 2)) * \
                               np.exp((-1 * (XX[0, col] ** 2 + YY[row, 0] ** 2) / (2 * sigm ** 2)))

    return -ker

def normalize(arr):
    """
    Normalize image between 0-255

    :param arr: Going to be normalized array
    :return: Normalized array
    """
    arr = arr / arr.max(initial=0) * 255
    arr = np.uint8(arr)
    return arr

def apply_filter(arr, ker):
    """
    Makes convolution operation

    :param arr: Going to be convolved
    :param ker: Convolution operation kernel
    :return: Convolved image
    """
    return sig.convolve2d(arr, ker, mode='same')

def show_image(*argv):
    """
    Plot images in order

    :param argv: Needs to be passed grayscale images
    """
    for arg in argv:
        plt.imshow(arg, cmap='gray')
        plt.show()

if __name__ == '__main__':
    try:
        img = cv2.imread('../test_images/test.png', cv2.IMREAD_GRAYSCALE)

        if img is None:
            raise ValueError("File don't exist!")

        else:
            sigma = 1
            kernel_shape = (7, 7)

            gaussian_kernel = gaussian_2d_kernel(kernel_shape, sigma)
            log_kernel = laplacian_of_gaussian_2d_kernel(kernel_shape, sigma)

            gaussian_result = normalize(apply_filter(img, gaussian_kernel))
            log_result = normalize(apply_filter(img, log_kernel))
            log_after_gaussian_result = normalize(apply_filter(apply_filter(img, gaussian_kernel), log_kernel))

            show_image(img, gaussian_result, log_result, log_after_gaussian_result)

    except ValueError as ve:
        print(ve)


import numpy as np

def sigmoid_activation(z):
    """
    Sigmoid (or “logit”) activation function.

    converts a continuous input into a probability value [0,1],
        which can be interpreted as the class probability,
        or the likelihood that the input example should be classified positively.
    Using this probability along with a threshold value, we can obtain a discrete label prediction.
    """
    return 1 / (1 + np.exp(-z))

# # visualize the sigmoid activation function’s output (to see what it’s really doing):
# nums = np.arange(-10, 10, step=1)
# fig, ax = plt.subplots(figsize=(12,8))
# ax.plot(nums, sigmoid(nums), 'r')

def mse_loss(X, y, theta):
    """
    Mean Squared Error (MSE) loss.
    The cost function for linear regression.
    """
    y_pred = X * theta.T
    error = y_pred - y
    return np.sum(np.power(error, 2)) / (2 * len(X))

def log_loss(theta, x, y, learning_rate=None):
    """
    Binary cross-entropy / log loss.
    The cost function for binary (linear) logistic regression.

    Evaluates the current model's parameters' performance on the training data.
    Determines: "given some candidate solution theta applied to input X,
        how far off is the result from the true desired outcome y".

    Note that we reduce the output down to a single scalar value, which is the sum of the “error” quantified as a
    function of the difference between the class probability assigned by the model and the true label of the example.
    The implementation is completely vectorized – it’s computing the model’s predictions for the whole dataset
    in one statement (sigmoid(X * theta.T)).
    the variable called “reg” is a function of the parameter values.
        As the parameters get larger, the penalization added to the cost function increases.
    the “learning rate” parameter is also part of the regularization term in the equation.
        The learning rate gives us a new hyper-parameter that we can use to tune how much weight the regularization holds in the cost function.
    """
    # theta = np.matrix(theta)
    # x = np.matrix(X)
    # y = np.matrix(y)
    first = np.multiply(-y, np.log(sigmoid_activation(x * theta.T)))
    second = np.multiply((1 - y), np.log(1 - sigmoid_activation(x * theta.T)))
    if learning_rate is None:
        return np.sum(first - second) / (len(x))
    else:
        reg = (learning_rate / 2 * len(x)) * np.sum(np.power(theta[:, 1:theta.shape[1]], 2))
        return np.sum(first - second) / (len(x)) + reg

def softmax_loss(theta, x, y, learning_rate=None):
    """
    Categorical cross-entropy / softmax loss.
    The cost function for multi-class logistic regression.
    """
    # theta = np.matrix(theta)
    # x = np.matrix(X)
    # y = np.matrix(y)
    first = np.multiply(-y, np.log(sigmoid_activation(x * theta.T)))
    second = np.multiply((1 - y), np.log(1 - sigmoid_activation(x * theta.T)))
    if learning_rate is None:
        return np.sum(first - second) / (len(x))
    else:
        reg = (learning_rate / 2 * len(x)) * np.sum(np.power(theta[:, 1:theta.shape[1]], 2))
        return np.sum(first - second) / (len(x)) + reg

def single_gradient_step(theta, x, y, learning_rate=None):  # gradient
    """
    a function to compute the gradient of the model parameters to figure out how to change the parameters
    to improve the outcome of the model on the training data.
    Recall that with gradient descent we don’t just randomly jigger around the parameter values and see what works best.
    At each training iteration we update the parameters in a way that’s guaranteed to move them in a direction
    that reduces the training error (i.e. the “cost”).
    We can do this because the cost function is differentiable.
    Note that we don't actually perform gradient descent in this function - we just compute a single gradient step.
    the gradient function specifies how to change those parameters to get an answer that's slightly better than the one we've already got
    """
    # theta = np.matrix(theta)
    # x = np.matrix(X)
    # y = np.matrix(y)

    error = sigmoid_activation(x * theta.T) - y

    # more generalized way:
    if learning_rate is None:
        grad = ((x.T * error) / len(x)).T
    else:
        reg = (learning_rate / len(x)) * theta
        grad = ((x.T * error) / len(x)).T + reg

    grad[0, 0] = np.sum(np.multiply(error, x[:, 0])) / len(x)  # intercept gradient is not regularized
    return np.array(grad).ravel()

    # more specific way:
    # parameters = int(theta.ravel().shape[1])
    # grad = np.zeros(parameters)
    # for i in range(parameters):
    #     term = np.multiply(error, X[:, i])
    #     if learning_rate is None or i == 0:  # the first parameter is not regularized, it's considered the “bias” or “intercept” of the model and shouldn’t be penalized
    #         grad[i] = np.sum(term) / len(X)
    #     else:
    #         reg = (learningRate / len(X)) * theta[:, i]
    #         grad[i] = (np.sum(term) / len(X)) + reg
    # return grad


import codecademylib3_seaborn
import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets
from copy import deepcopy

iris = datasets.load_iris()

samples = iris.data
samples = iris.data

x = samples[:,0]
y = samples[:,1]

sepal_length_width = np.array(list(zip(x, y)))

# Step 1: Place K random centroids

k = 3

centroids_x = np.random.uniform(min(x), max(x), size=k)
centroids_y = np.random.uniform(min(y), max(y), size=k)

centroids = np.array(list(zip(centroids_x, centroids_y)))

# Step 2: Assign samples to nearest centroid

def distance(a, b):
  one = (a[0] - b[0]) **2
  two = (a[1] - b[1]) **2
  distance = (one+two) ** 0.5
  return distance

# Cluster labels for each point (either 0, 1, or 2)
labels = np.zeros(len(samples))

# Distances to each centroid
distances = np.zeros(k)

for i in range(len(samples)):
  distances[0] = distance(sepal_length_width[i], centroids[0])
  distances[1] = distance(sepal_length_width[i], centroids[1])
  distances[2] = distance(sepal_length_width[i], centroids[2])
  cluster = np.argmin(distances)
  labels[i] = cluster

# Step 3: Update centroids

centroids_old = deepcopy(centroids)

for i in range(k):
  points = [sepal_length_width[j] for j in range(len(sepal_length_width)) if labels[j] == i]
  centroids[i] = np.mean(points, axis=0)

print(centroids_old)
print("- - - - - - - - - - - - - -")
print(centroids)

Data Scientist Career Path/12. Foundations of Machine Learning Unsupervised Learning/1. KMeans/5. step3.py


import numpy as np

def computeCost(X, y, theta):
    m = len(X)
    # Matrix multiplication of X and theta which is the hypothesis function
    hypothesis_X = np.matmul(X, theta)
    #print('shape', hypothesis_X.shape)
    # Calculating difference of hypothesis and y
    dif = hypothesis_X - y
    # print('shape', dif.shape)
    # Getting the Square of each individual element value
    # Element wise product - we can also use np.multiply(dif, dif)
    dif_sqr = dif * dif
    # Generating a row vector to multiply to get the sum of all the elements in the matrix
    #print('shape', dif_sqr.shape)
    ones = np.ones(m)
    #print('shape', ones.shape)
    # Multiplying row vector and difference square to get cost vector
    cost_mat = (0.5/m) * np.matmul(ones, dif_sqr)
    # Converting matrix 1 by 1 Ex: [5] to scalar value 5
    cost_scalar = np.asscalar(cost_mat)
    return cost_scalar


'''
Power Iteration method of finding dominant eigenvector.
	source : https://www.wikiwand.com/en/Power_iteration
'''
import numpy as np
from numpy.linalg import norm

def power_iteration( A, b0, tol = 0.0000001 ):
	n = len( b0 )
	b1 = getBi( A, b0, n, tol )
	while norm( np.subtract( b1, b0 ), ord = n ) > tol:
	# for i in range(10000):
		b0 = b1
		b1 = getBi( A, b0, n )
	return b1

def getBi( A, b, n, tol = 0.0000001 ):
	b = np.dot( A, b )
	b_norm = norm( b, ord = n )
	if b_norm < tol:
		b /= 0.0000001
	else:
		b /= b_norm
	return b

def __test():
	A = np.array( [ [ 0.5, 0.5 ], [ 0.2, 0.8 ] ] )
	b = np.random.rand( 2 )
	return power_iteration( A, b )#, np.linalg.eig( A )[1]

def __random_test( n ):
	A = np.random.rand( n, n )
	b = np.random.rand( n )
	return power_iteration( A, b )#, np.linalg.eig( A )[1]

if __name__ == '__main__':
	print __test()
	print __test()
	for i in range(10, 151):
		__random_test( i )
	print 'Random square matrices of sizes 10-150 completed.'


"""
Objetivo: Resolver questão 2a do terceiro laboratorio.
    é exatamente o mesmo codigo da questão anterior porém mudando a função a ser passada e o palpite
"""
import matplotlib.pyplot as plt
import numpy as np
from Newton_Raphson import newton_raphson

#PARA X0 = 1
y = lambda x: 180*x**3 - 117*x**2 - 80*x + 52  # f(x) = 180x^3 - 117x^2 - 80x + 52
y_linha = lambda x: 540*x**2 - 234*x - 80  # f'(x) = 540x^2 - 234x - 80
ini = 1  # ponto inicial
#Verificar o arquivo Nexton-Raphson.py para entender o funcionamento dessa função
resp = newton_raphson(ini, y, y_linha, 'q2')
print(f'Para essa X0 = 1 levamos {resp["iterações"] - 1} iterações e temos como valor de f(x) = {resp["modulo"]}')
print('Abaixo seguem os graficos dos valores de f(x) e dos erros, respectivamente:')

# Grafico com os valores de f(x)
x_a = np.linspace(-1.2, 1.2, 30)  # valores escolhidos para melhor visualização do grafico
y_a = [y(i) for i in x_a]
plt.style.use('ggplot')
plt.figure(figsize=(7, 5))
plt.title(f'F(x) por X\ncom X0 = 1')
plt.xlabel('Valores de x')
plt.ylabel('Valores de f(x)')
# função com valores entre 0.4 e 0.8
plt.plot(x_a, y_a, label='Função exata')
# Nossa aproximação Newton-Raphson por iteração
plt.plot(resp['valores'], resp['valores_função'], label='Valores de Newton-Raphson por iteração')
plt.tight_layout()
plt.legend(loc='best')
plt.show()

# Grafico com os valores dos erros
plt.style.use('ggplot')
plt.figure(figsize=(7, 5))
plt.title('Erro por Iterações')
plt.xlabel('Numero de iterações')
plt.ylabel('Valores dos erros')
plt.plot([i for i in range(1, resp['iterações'])], resp['erros'])
plt.tight_layout()
plt.show()

#PARA X0 = -1
y = lambda x: 180*x**3 - 117*x**2 - 80*x + 52  # f(x) = 180x^3 - 117x^2 - 80x + 52
y_linha = lambda x: 540*x**2 - 234*x - 80  # f'(x) = 540x^2 - 234x - 80
ini = -1  # ponto inicial
#Verificar o arquivo Nexton-Raphson.py para entender o funcionamento dessa função
resp = newton_raphson(ini, y, y_linha, 'q2')
print(f'Para essa X0 = -1 levamos {resp["iterações"] - 1} iterações e temos como valor de f(x) = {resp["modulo"]}')
print('Abaixo seguem os graficos dos valores de f(x) e dos erros, respectivamente:')

# Grafico com os valores de f(x)
x_a = np.linspace(-1.2, 1.2, 30)  # valores escolhidos para melhor visualização do grafico
y_a = [y(i) for i in x_a]
plt.style.use('ggplot')
plt.figure(figsize=(7, 5))
plt.title(f'F(x) por X\ncom X0 = -1')
plt.xlabel('Valores de x')
plt.ylabel('Valores de f(x)')
# função com valores entre 0.4 e 0.8
plt.plot(x_a, y_a, label='Função exata')
# Nossa aproximação Newton-Raphson por iteração
plt.plot(resp['valores'], resp['valores_função'], label='Valores de Newton-Raphson por iteração')
plt.tight_layout()
plt.legend(loc='best')
plt.show()

# Grafico com os valores dos erros
plt.style.use('ggplot')
plt.figure(figsize=(7, 5))
plt.title('Erro por Iterações')
plt.xlabel('Numero de iterações')
plt.ylabel('Valores dos erros')
plt.plot([i for i in range(1, resp['iterações'])], resp['erros'])
plt.tight_layout()
plt.show()


#!/usr/bin/python

import sys
import time
import numpy as np

print('MATRIZ UNIDIMENCIONAL Y BIDIMENCIONAL')
a=np.array([1,2,3])
print('1D array:')
print(a)
print()
b=np.array([(1,2,3),(4,5,6)])
print('2D array:')
print(b)

print('-------------')
print('LISTA DE MIL NUMEROS Y CALCULAMOS LA MEMORIA ASIGNADA A ESTA')
s = range(1000)
print('Resultado lista de Python:')
print(sys.getsizeof(5)*len(s))
print()
d = np.arange(1000)
print('Resutado NumPy array:')
print(d.size*d.itemsize)

print('-------------')
print('MODULO PARA EVALUAR LA RAPIDEZ EN LA QUE SE EJECUTAN ESTAS INSTRUCCIONES')
SiZE = 1000000
L1 = range(SiZE)
L2 = range(SiZE)
A1 = np.arange(SiZE)
A2 = np.arange(SiZE)

start = time.time()
result = [(x,y) for x,y in zip(L1, L2)]
print('Resultado lista de Python:')
print((time.time()-start)*1000)
print()
start = time.time()
result = A1 + A2
print('Resultado NumPy array:')
print((time.time()-start)*1000)

print('-------------')
print('CREAR UNA MATRIZ DE UNOS --> 3 FILAS Y 4 COLUMNAS ')
unos = np.ones((3,4))
print(unos)

print('CREAR UNA MATRIZ DE CEROS --> 3 FILAS Y 4 COLUMNAS ')
ceros = np.zeros((3,4))
print(ceros)

print('CREAR UNA MATRIZ DE NUMEROS ALEATORIOS --> 3 FILAS Y 4 COLUMNAS ')
aleatorios = np.random.random((3,4))
print(aleatorios)

print('CREAR UNA MATRIZ VACIA --> 3 FILAS Y 4 COLUMNAS ')
vacia = np.empty((3,4))
print(vacia)

print('CREAR UNA MATRIZ DE UN SOLO VALOR --> 3 FILAS Y 4 COLUMNAS ')
full = np.full((3,4),5)
print(full)

print('CREAR UNA MATRIZ CON VALORES ESPACIADOS UNIFORMEMENTE')
"""
Realiza una matriz de los números que se encuentran entre el cero y 30
haciendo saltos de 5 en 5
"""
espacio1 = np.arange(0,30,5)
print(espacio1)
"""
Realiza una matriz de los de 5 valores con los números que se encuentran
entre el 0 y 2
"""
espacio2 = np.linspace(0,2,5) #
print(espacio2)

print('CREAR UNA MATRIZ ENTIDAD')
identidad1 = np.eye(4,4)
print(identidad1)
identidad2 = np.identity(4)
print(identidad2)

print('CONOCER LAS DIMENCIONES DE UNA MATRIZ')
a = np.array([(1,2,3),(4,5,6)])
print(a.ndim)
print('CONOCER EL TIPO DE LOS DATOS')
print(a.dtype)
print('CONOCER EL TAMAÑO DE LA MATRIZ')
print(a.size)
print('CONOCER EL FORMA DE LA MATRIZ')
print(a.shape)

print('CAMBIO DE FORMA DE UNA MATRIZ')
print(a)
b = a.reshape(3,2)
print(b)

"""
Operaciones matemáticas
"""
print('ENCONTRAR EL MINIMO, MAXIMO Y SUMA')
c = np.array([9,8,7,6,5,4,3,2,1])
print(c.min())
print(c.max())
print(c.sum())
print('CALCULAR LA RAÍZ CUADRADA Y DESVIACIÓN ESTÁNDAR')
d = np.array([(9,8,7,6,5), (4,3,2,1,0)])
print(np.sqrt(d))
print(np.std(d))
print('CALCULAR LA SUMA, RESTA, MULTIPLICACIÓN Y DIVISIÓN DE DOS MATRICES')
x = np.array([(1,3,5,7,9),(2,4,6,8,10)])
y = np.array([(10,9,8,7,6), (5,4,3,2,1)])
print(x+y)
print(x-y)
print(x*y)
print(x/y)


"""
Demonstrate Calculations with Complex Numbers
Learning Example for Dr. Stanier's classes
File: E7_complex_numbers.py

Author: Charles Stanier, charles-stanier@uiowa.edu
Date:  August 27, 2019
Written/Tested In: Python 3.7.3

Program Objective: Demonstrate how to manipulate complex numbers
Showing some of the features of cmath at https://docs.python.org/2/library/cmath.html

Modifications: none so far

"""

import numpy as np  # this is used for math functions
import pylab as pl  # this is used for plotting functions

# super simple, let's take the square root of -1
x = -1
# y = np.sqrt(x) # this generates and error
y1 = x**0.5

# but if we define x as complex and use np.sqrt it works
x = -1+0j
y2 = np.sqrt(x)

print('Square root of -1 using method 1: ', y1)
print('Square root of -1 using method 2: ', y2)

# extract the real and imaginary portions and print

y2r = np.real(y2)
y2i = np.imag(y2)

print('y2 is equal to ',y2r,' plus ',y2i,'i')

# now lets deal with a vector, take the square root, and plot on complex plane
real_vec = np.linspace(-5,5,100)
imag_vec = np.ones(real_vec.size)*1j  # j is "i" the sqrt of -1
x_vec = real_vec + imag_vec
# take the square root of those 100 values
x_sqrt = np.sqrt(x_vec)

# for complex plot, we make the real part the x vector
# and the imag part the y vector
plotx = np.real(x_sqrt)
ploty = np.imag(x_sqrt)

# use pylab to plot x and y
pl.plot(plotx, ploty, 'o', color='black')
pl.xlabel('Real Portion')
pl.ylabel('Imag Portion')

# show the plot on the screen
pl.show()


import math
import pandas as pd
import numpy as np

def prime(num):
    if num >= 1:
        root_num = int(math.sqrt(num))
        # print(root_num)
        for i in range(2, root_num+1, 1):
            # print(i)
            if (num % i) == 0:
                print("Number is not prime")
                break
        else:
            print("Prime Number")

lst = []
def prime_in_range(num_till):
    for i in range(num_till+1):
        if i>=1:
            root_num = int(math.sqrt(i))
            for j in range(2,root_num,1):
                if i%j==0:
                    # print("Not Prime Number")
                    break
            else:
                lst.append(i)

def factorial(num):
    """This is a recursive function that calls
   itself to find the factorial of given number"""

    if num == 1:
        return num
    else:
        # print("lofh")
        return num * factorial(num-1)

if __name__ == "__main__":
    num = int(input("Enter number to find  : "))

    prime(num)
    prime_in_range(num)
    print("Prime Numbers in the Range : ", lst)
    if num < 0:
        print("Factorial cannot be found for negative numbers")
    elif num == 0:
        print("Factorial of 0 is 1")
    else:
        print("Factorial of", num, "is: ", factorial(num))


#!/usr/bin/env python3

# Instalação
# - pip install -r requirements.txt
# - pip install numpy

import numpy as np

# Numpy: biblioteca para computação científica - funções bem otimizadas
a = np.array([1, 2, 3])
print(type(a))
print(a.shape)
print(a[0], a[1], a[2])

a[0] = 5
print(a)

b = np.array([[1, 2, 3],
              [4, 5, 6]])
print(b.shape)
print(b[0, 0], b[0, 1], b[1, 0])

# Tupla: tipo de dados imutável
tupla = (1, 2, 3, 4)
print(tupla[0])

# Cria uma matriz com zeros
a = np.zeros((2, 2))
print(a)

# Cria uma matriz com uns
b = np.ones((2, 2))
print(b)

# Cria uma matriz com 7s
c = np.full((2, 2), 7)
print(c)

# Cria uma matriz identidade
d = np.eye(2)
print(d)

# Cria uma matriz aleatória
e = np.random.random((2, 2))
print(e)

# Seleciona uma fatia da matriz
a = np.array([[1, 2, 3, 4],
              [5, 6, 7, 8],
              [9, 10, 11, 12]])
b = a[:2, 1:3]
print(b)

# lista[2:3]

print('A =\n', a)
print('B =\n', b)

b[0, 0] = 77

print('A =\n', a)

# Indexação booleana
a = np.array([[1, 2], [3, 4], [5, 6]])

bool_idx = (a > 2)
print(bool_idx)

print(a[bool_idx])
print(a[a > 2])

# Tipos de dados
x = np.array([1, 2])
print(x.dtype)

x = np.array([1.0, 2.0])
print(x.dtype)

x = np.array([1, 2], dtype=np.int64)
print(x.dtype)

# """### Operações"""

x = np.array([[1, 2], [3, 4]], dtype=np.float64)
y = np.array([[5, 6], [7, 8]], dtype=np.float64)

# print(x + y)
print(np.add(x, y))

# print(x - y)
print(np.subtract(x, y))

# print(x * y)
print(np.multiply(x, y))

# print(x / y)
print(np.divide(x, y))

print(np.sqrt(x))

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

print(x.dot(y))
# print(np.dot(x, y))

v = np.array([9, 10])
w = np.array([11, 12])

print(v.dot(w))
# print(np.dot(v, w))

# Transposta de uma matriz
x = np.array([[1, 2], [3, 4]])

print(x)
print(x.T)


import numpy as np

class Activation:
    def step_function(self,x):
        y = x > 0
        return y.astype(np.int)

    #왜 sigmoid?
    # 출력이 0~1 사이이고, 미분이 쉬운 함수들중 가장 그럴듯 해서 많이 사용하게됨. 미분은 exp이 쉽기때문에
    def sigmoid(self,x):
        return 1/(1 + np.exp(-x))

    def RelU(self,x):
        return np.maximum(0,x)

     #왜 softmax?
    # 출력이 0~1 사이이고, 미분이 쉬운 함수들중 가장 그럴듯 해서 많이 사용하게됨. 미분은 exp이 쉽기때문에
    def softMax(self,xs):
        mx = np.max(xs)
        expx = np.exp( xs - mx )
        expsum = np.sum(expx)
        y = expx / expsum
        return y

if __name__ == "__main__":
    a = [ 1 ,3]
    print(Activation().softMax(a))


#Use of classes, 1: initialization part, we tìdefine the input of parameters, for axample the matrix A or the vector y.

import numpy as np
import matplotlib.pyplot as plt

class SolveMinProbl:
    def __init__(self, y=np.ones((6, 1)), A=np.eye(5)): #initialization
        self.matr=A
        self.Np=y.shape[0]
        self.Nf=A.shape[1]
        self.vect=y
        self.sol=np.zeros((self.Nf, 1), dtype=float)
        return
    def plot_w(self, title='Solution'):
        w=self.sol
        n=np.arange(self.Nf)
        plt.figure()
        plt.plot(n, w)
        plt.xlabel('n')
        plt.ylabel('w(n)')
        plt.title(title)
        plt.grid()
        plt.show()
        return
    def print_result(self, title):
        print(title, '_:')
        print("The optimum weight vector is:_")
        print(self.sol)
        return

    def plot_err(self, title='Square_error', logy=0, logx=0):
        err=self.err
        plt.figure()
        if (logy==0) and (logx==0):
            plt.plot(err[:, 0], err[:, 1])
        if (logy == 1) and (logx == 0):
            plt.plot(err[:, 0], err[:, 1])
        if (logy == 0) and (logx == 1):
            plt.semilogy(err[:, 0], err[:, 1])
        if (logy == 1) and (logx == 1):
            plt.loglog(err[:, 0], err[:, 1])
        plt.xlabel('n')
        plt.ylabel('e(n)')
        plt.title(title)
        plt.margins(0.01, 0.1)
        plt.grid()
        plt.show()
        return

class SolveLLS(SolveMinProbl):
    def run(self):
        A=self.matr
        y=self.vect
        w=np.dot(np.dot(np.linalg.inv(np.dot(A.T, A)), A.T), y)
        self.sol=w
        self.min=np.linalg.norm(np.dot(A, w)-y)

class SolveGrad(SolveMinProbl):
    def run(self, gamma, Nit=1000):
        self.err=np.zeros((Nit, 2), dtype=float)
        A=self.matr
        y=self.vect
        w=np.random.rand(self.Nf, 1)
        for it in range(Nit):
            grad=2*np.dot(A.T, (np.dot(A,w)-y))
            w=w-gamma*grad
            self.err[it, 0]=it
            self.err[it, 1]=np.linalg.norm(np.dot(A, w)-y)
        self.sol=w
        self.min=self.err[it, 1]

class SolveSteep(SolveMinProbl):
    def run(self, Nit=1000):
        self.err = np.zeros((Nit, 2), dtype=float)
        A = self.matr
        y = self.vect
        w = np.random.rand(self.Nf, 1)
        for it in range(Nit):
            grad = 2 * np.dot(A.T, (np.dot(A, w) - y))
            H=4*np.dot(A.T, A)
            w=w-np.linalg.norm(grad)**2/np.dot(np.dot(grad.T, H), grad)*grad
            self.err[it, 0] = it
            self.err[it, 1] = np.linalg.norm(np.dot(A, w) - y)
        self.sol = w
        self.min = self.err[it, 1]

class SolveStocha(SolveMinProbl):
    def run(self,Nit=100, gamma=1e-3):
        self.err=np.zeros((Nit, 2), dtype=float)
        A=self.matr
        y=self.vect
        w=np.random.rand(self.Nf, 1)
        for it in range(Nit):
            for i in range(self.Nf):
                grad=gamma*(np.dot(A[i], w)-y[i])*A[i]
                w=w-grad
            self.err[it, 0]=it
            self.err[it, 1]= np.linalg.norm(np.dot(A, w) - y)
        self.sol=w
        self.min=self.err[it, 1]

if __name__=="__main__":
    Np=7
    Nf=7
    A=np.random.randn(Np, Nf)
    y=np.random.randn(Np, 1)
    m=SolveLLS(y, A)
    m.run()
    m.print_result('LLS')
    m.plot_w('LLS')

    gamma=1e-2
    g=SolveGrad(y, A)
    g.run(gamma)
    g.print_result('Gradient_algo')
    logx=0
    logy=1
    g.plot_err('Gradient_algo:_square_error', logx, logy)

    s=SolveSteep(y, A)
    s.run()
    s.print_result('SDA')
    s.plot_err('Steepest_decent_algo:_square_error', logx, logy)

    st=SolveStocha(y, A)
    st.run()
    s.plot_err('Stochastic Algorithm', logx, logy)
    s.print_result('SA')

#with no.random.seed(N) i obtain also the same random values


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

"""
X - observation matrix:
    rows - observations; n - number of rows
    cols - values/factors observed

@ operator - matrix multiplication in numpy
"""

# np.loadtxt(open("cereal_cut.csv", "rb"), delimiter=",")

def center_matrix(X):
    """
    centers a matrix by subtracting matrix A with values 1/n from it
    :param X: :type numpy array: input matrix
    :return: :type numpy array:  - centered matrix
    """
    n = len(X)
    A = np.divide(np.ones((n, n)), n)
    M = A @ X

    return X - M

def reduce(X, threshold = 1, leave = None):
    """
    reduces the dimensionality of a observed data by removing the least significant characteristics
    :param X: :type numpy array: input matrix
    :param threshold: :type float: cutoff value for eigenvalues to be considered significant
    :param leave: :type int: fallback if nothing is cut off, leave a fixed number of the most significant axes, None - don't cut off anything
    :return: :type numpy array: reduced matrix
    """
    n = len(X)
    X_hat = center_matrix(X)

    # Sigma
    cov_mtx = np.cov(X_hat, rowvar = False) # (X_hat.transpose() @ X_hat) * (1 / n)  -- difrerent results ???

    cov_eVal, cov_eVec = np.linalg.eigh(cov_mtx)

    cutoff_index = 0
    for ind,eVal in enumerate(cov_eVal):
        if eVal > threshold:
            cutoff_index = ind
            break

    if cutoff_index == 0 and leave:
        cutoff_index = len(eVal) - leave if len(eVal) - leave >= 0 else 0

    return X_hat @ cov_eVec[ : , cutoff_index : ]

if __name__ == "__main__":
    d = np.loadtxt(open("./data/cereal_cut.csv", "rb"), delimiter=",", skiprows=1)
    print(d.shape, d)
    res = reduce(d)
    print(res.shape, res)


import numpy as np
import matplotlib.pyplot as plt

# The function we Want to Solve
def f(x):
    return np.tan(x) - x / (1 - x**2)

if __name__ == "__main__":

    # Plot the Function to Visualize the Root
    x = np.linspace(2,4.5,1000)
    y = f(x)

    plt.plot(x, y)
    plt.xlabel('x')
    plt.ylabel('f(x)')
    plt.tight_layout
    plt.show()

    # Repetitions
    N = 20

    # Region Edges (Find Them in the Diagram of the Function)
    a, b = [2], [4]

    # Counters
    i, j = 0, 0

    for n in range(N):
        # Center of Interval (a,b)
        c = 0.5 * (a[i] + b[j])
        # Where is the Root? In (a,c) or in (c,b)? (Bolzano Theorem)
        if np.sign(f(a[i])) * np.sign(f(c)) == -1.0:
            b += [c]
            j += 1
        elif np.sign(f(c)) * np.sign(f(b[j])) == -1.0:
            a += [c]
            i += 1

    # We Specify the Root as the Center of the Interval (a,b)
    root = 0.5 * (b[j] + a[i])

    print(f'The root of f is: x = {root:.4f}.')
    print(f'The valus of f for the above x is {f(root):.7f}.')


import numpy as np
import matplotlib.pyplot as plt
from scipy import linalg as la

def PCA(dat,center=False,percentage=0.8):
    M=dat[:,0].size
    N=dat[0,:].size
    if center:
	    mu = np.mean(dat,0)
	    dat -= mu

    U,L,Vh = la.svd(dat,full_matrices=False)

    V = (Vh.T).conjugate()
    SIGMA = np.diag(L)
    X = np.dot(U,SIGMA)
    Lam = L**2

    csum = [np.sum(Lam[:i+1])/np.sum(Lam) for i in range(N)]

    normalized_eigenvalues = Lam/np.sum(Lam)
    n_components = np.array([x < percentage for x in csum]).tolist().index(False)

    return (normalized_eigenvalues,
            V[:,0:n_components],
            SIGMA[0:n_components,0:n_components],
            X[:,0:n_components])

def scree(normalized_eigenvalues):
    plt.plot(normalized_eigenvalues,'b-',normalized_eigenvalues,'bo')
    plt.xlabel("Principal Components")
    plt.ylabel("Percentage of Variance")
    plt.show()
    return


#!usr/bin/env python3

import numpy as np
input_matrix = np.array([
    [1, 0.3, 0.3, 0.3],
    [0.3, 1, 0.3, 0.3],
    [0.3, 0.3, 1, 0.3],
    [0.3, 0.3, 0.3, 1]
    ])

def cholesky_decomp(correlationmatrix):
    cholmat = np.zeros_like(correlationmatrix)
    for i in range(len(cholmat)):
        for j in range(i+1):
            if i==j:
                #this computes the diagonal values
                diag = correlationmatrix[i,i] - np.sum(np.square(cholmat[i,:i]))
                #because you cannot sqrt a negative number:
                if diag<0:
                    return 0.0
                cholmat[i,i] = np.sqrt(diag)
            else:
                #computing the rest of the matrix
                cholmat[i,j] = (correlationmatrix[i,j] - np.sum(cholmat[i,:j]*cholmat[j,:j]))/cholmat[j,j]
    return cholmat

cholesky_from_function = cholesky_decomp(input_matrix)
cholesky_from_np = np.linalg.cholesky(input_matrix)
check = np.allclose(cholesky_from_function,cholesky_from_np)

print("\nCorrelation matrix:")
print(input_matrix)
print("\nCholesky from function:")
print(cholesky_from_function)
print("\nCholesky from numpy:")
print(cholesky_from_np)
print("\nAre the two cholesky matrices essentially identical??")
print(check)


from __future__ import division
import numpy as np
from scipy.sparse import spdiags
from scipy.sparse.linalg import spsolve, cg
import matplotlib.pyplot as plt

def fd_order2_ode(func,a1,a2,a3,a=0.,b=1.,alpha=1.,beta=3.,N=5):
	# A Simple Finite Difference Scheme to solve BVP's of the form
	# a1(x)u''(x) + a2(x)u'(x) + a3(x)u(x) = f(x), x \in [a,b]
	# u(a) = alpha
	# u(b) = beta
	# (Dirichlet boundary conditions)
	#
	# U_0 = alpha, U_1, U_2, ..., U_m, U_{m+1} = beta
	# We use m+1 subintervals, giving m algebraic equations
	m = N-1
	h = (b-a)/N		  # Here we form the diagonals
	x = np.linspace(a,b,N+1)
	D0,Dp,Dm,diags = np.zeros((1,m)), np.zeros((1,m)), np.zeros((1,m)), np.array([0,-1,1])

	D0 += -2.*a1(x[1:-1])*h**(-2.) + a3(x[1:-1])
	Dm += a1(x[1:-1])*h**(-2.) - a2(x[1:-1])*(2.*h)**(-1.)
	Dp += a1(x[1:-1])*h**(-2.) + a2(x[1:-1])*(2.*h)**(-1.)
	# print "\nD0 = \n", D0[0,:5]
	# print "\nDm = \n", Dm[0,:5]
	# print "\nDp = \n", Dp[0,:5]
	# Here we create the matrix A
	data = np.concatenate((D0,np.roll(Dm,-1),np.roll(Dp,1)),axis=0) # This stacks up rows
	A = spdiags(data,diags,m,m).asformat('csr')
	print "\nA = \n", A[:5,:5].todense()
	print "\nA = \n", A[-5:,-5:].todense()
	# print A.shape
	# Here we create the vector B
	B = np.zeros(N+1)
	B[2:-2] = func(x[2:-2])
	xj = a+1.*h
	B[0], B[1] = alpha, func(xj)-alpha *( a1(xj)*h**(-2.) - a2(xj)*(2.*h)**(-1.) )
	xj = a+m*h
	B[-1], B[-2]  = beta, func(xj)-beta*( a1(xj)*h**(-2.) + a2(xj)*(2.*h)**(-1.) )
	print "\nB = \n", B[:5]
	print "\nB = \n", B[-5:]

	# Here we solve the equation AX = B and return the result
	B[1:-1] = spsolve(A,B[1:-1])
	return np.linspace(a,b,m+2), B

def approx_order(num_approx,N,bvp,*args):
	h, max_error = (1.-0)/N[:-1], np.ones(num_approx-1)

	mesh_best, num_sol_best = bvp(*args, subintervals=N[-1])
	for j in range(len(N)-1):
		mesh, num_sol = bvp(*args, subintervals=N[j])
		max_error[j] = np.max(np.abs( num_sol- num_sol_best[::2**(num_approx-j-1)] ) )
	plt.loglog(h,max_error,'.-r',label="$E(h)$")
	plt.loglog(h,h**(2.),'-k',label="$h^{\, 2}$")
	plt.xlabel("$h$")
	plt.legend(loc='best')
	plt.show()
	print "The order of the finite difference approximation is about ", ( (np.log(max_error[0]) -
		np.log(max_error[-1]) )/( np.log(h[0]) - np.log(h[-1]) ) ), "."

#
# def example():
#	# First Code block in the lab manual
#	import numpy as np
#	from scipy.sparse import spdiags
#	from scipy.sparse.linalg import spsolve
#
#	def bvp(func, epsilon, alpha, beta, N):
#		a,b = 0., 1.	# Interval for the BVP
#		h = (b-a)/N		# The length of each subinterval
#
#		# Initialize and define the vector F on the right
#		F = np.empty(N-1.)
#		F[0] = func(a+1.*h)-alpha*(epsilon+h/2.)*h**(-2.)
#		F[N-2] = func(a+(N-1)*h)-beta*(epsilon-h/2.)*h**(-2.)
#		for j in xrange(1,N-2):
#			F[j] = func(a + (j+1)*h)
#
#		# Here we define the arrays that will go on the diagonals of A
#		data = np.empty((3,N-1))
#		data[0,:] = -2.*epsilon*np.ones((1,N-1)) # main diagonal
#		data[1,:]  = (epsilon+h/2.)*np.ones((1,N-1))	 # off-diagonals
#		data[2,:] = (epsilon-h/2.)*np.ones((1,N-1))
#		# Next we specify on which diagonals they will be placed, and create A
#		diags = np.array([0,-1,1])
#		A=h**(-2.)*spdiags(data,diags,N-1,N-1).asformat('csr')
#
#		U = np.empty(N+1)
#		U[1:-1] = spsolve(A,F)
#		U[0], U[-1] = alpha, beta
#		return np.linspace(a,b,N+1), U
#
#	x, y = bvp(lambda x:-1., epsilon=.05,alpha=1, beta=3, N=400)
#	import matplotlib.pyplot as plt
#	plt.plot(x,y,'-k',linewidth=2.0)
#	plt.show()
#
#	num_approx = 5 # Number of Approximations
#	N = 20*np.array([2**j for j in range(num_approx)])
#	h, max_error = (1.-0)/N[:-1], np.ones(num_approx-1)
#
#	mesh_best, num_sol_best = bvp(lambda x:-1, epsilon=.5, alpha=1, beta=3, N=N[-1])
#	for j in range(len(N)-1):
#		mesh, num_sol = bvp(lambda x:-1, epsilon=.5, alpha=1, beta=3, N=N[j])
#		max_error[j] = np.max(np.abs( num_sol- num_sol_best[::2**(num_approx-j-1)] ) )
#	plt.loglog(h,max_error,'.-r',label="$E(h)$")
#	plt.loglog(h,h**(2.),'-k',label="$h^{\, 2}$")
#	plt.xlabel("$h$")
#	plt.legend(loc='best')
#	plt.show()
#	print "The order of the finite difference approximation is about ", ( (np.log(max_error[0]) -
#		np.log(max_error[-1]) )/( np.log(h[0]) - np.log(h[-1]) ) ), "."
#	return
#


import numpy as np
try:
    import matplotlib.pyplot as plt
except:
    import matplotlib
    matplotlib.use('Agg')
    import matplotlib.pyplot as plt

class PolynomialRegression():
    def __init__(self, degree):
        """
        Implement polynomial regression from scratch.

        This class takes as input "degree", which is the degree of the polynomial
        used to fit the data. For example, degree = 2 would fit a polynomial of the
        form:

            ax^2 + bx + c

        Your code will be tested by comparing it with implementations inside sklearn.
        DO NOT USE THESE IMPLEMENTATIONS DIRECTLY IN YOUR CODE. You may find the
        following documentation useful:

        https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html
        https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html

        Here are helpful slides:

        http://interactiveaudiolab.github.io/teaching/eecs349stuff/eecs349_linear_regression.pdf

        The internal representation of this class is up to you. Read each function
        documentation carefully to make sure the input and output matches so you can
        pass the test cases. However, do not use the functions numpy.polyfit or numpy.polval.
        You should implement the closed form solution of least squares as detailed in slide 10
        of the lecture slides linked above.

        Usage:
            import numpy as np

            x = np.random.random(100)
            y = np.random.random(100)
            learner = PolynomialRegression(degree = 1)
            learner.fit(x, y) # this should be pretty much a flat line
            predicted = learner.predict(x)

            new_data = np.random.random(100) + 10
            predicted = learner.predict(new_data)

            # confidence compares the given data with the training data
            confidence = learner.confidence(new_data)

        Args:
            degree (int): Degree of polynomial used to fit the data.
        """
        self.features = []
        self.targets = []
        self.degree = degree
        self.coefficients = []

    def fit(self, features, targets):
        """
        Fit the given data using a polynomial. The degree is given by self.degree,
        which is set in the __init__ function of this class. The goal of this
        function is fit features, a 1D numpy array, to targets, another 1D
        numpy array.

        Args:
            features (np.ndarray): 1D array containing real-valued inputs.
            targets (np.ndarray): 1D array containing real-valued targets.
        Returns:
            None (saves model and training data internally)
        """
        self.targets = targets
        x_arr = []
        for feature in range(features.size):
            powers = np.arange(0, self.degree+1)
            x_powers = np.power(np.full(self.degree+1, features[feature]), powers)
            x_arr.append(x_powers)
        x_arr = np.array(x_arr)
        self.features = x_arr
        x_transpose = np.transpose(x_arr)
        first = np.linalg.inv(np.matmul(x_transpose, x_arr))
        sec = np.matmul(x_transpose, targets)
        self.coefficients = np.matmul(first, sec)

    def predict(self, features):
        """
        Given features, a 1D numpy array, use the trained model to predict target
        estimates. Call this after calling fit.

        Args:
            features (np.ndarray): 1D array containing real-valued inputs.
        Returns:
            predictions (np.ndarray): Output of saved model on features.
        """
        x_arr = []
        for feature in range(features.size):
            powers = np.arange(0, self.degree+1)
            x_powers = np.power(np.full(self.degree+1, features[feature]), powers)
            x_arr.append(x_powers)
        x_arr = np.array(x_arr)
        predictions = np.matmul(x_arr, self.coefficients)
        return predictions

    def visualize(self, features, targets):
        """
        This function should produce a single plot containing a scatter plot of the
        features and the targets, and the polynomial fit by the model should be
        graphed on top of the points.

        DO NOT USE plt.show() IN THIS FUNCTION. Instead, use plt.savefig().

        Args:
            features (np.ndarray): 1D array containing real-valued inputs.
            targets (np.ndarray): 1D array containing real-valued targets.
        Returns:
            None (plots to the active figure)
        """
        x = features
        y = self.predict(features)
        plt.title('Polynomial Regression')
        plt.plot(x, y)
        plt.savefig('Polynomial Regression')


import numpy as np

def point_bilinear(x,y,z, pointX, pointY):
    i = index_vec(x, pointX)
    j = index_vec(y, pointY)

    l_ij = (x[i] - pointX)*(y[j] - pointY)
    l_i1j1 = (x[i+1] - pointX)*(y[j+1] - pointY)
    l_i1j = (x[i+1] - pointX)*(y[j] - pointY)
    l_ij1 = (x[i] - pointX)*(y[j+1] - pointY)

    z_ij = z[i,j]
    z_i1j1 = z[i+1,j+1]
    z_i1j = z[i+1,j]
    z_ij1 = z[i, j+1]

    num_1 = l_ij*z_i1j1 + l_i1j1*z_ij
    num_2 = l_i1j*z_ij1 + l_ij1*z_i1j
    den = (x[i+1] - x[i]) * (y[j+1] - y[j])

    return (num_1 - num_2) / den

def bilinear(x, y, z, vectX, vectY):
    dimX, dimY = len(vectX), len(vectY)
    bi = np.zeros((dimX, dimY))
    for indexX in range(dimX):
        for indexY in range(dimY):
            bi[indexX, indexY] = point_bilinear(x, y, z, vectX[indexX], vectY[indexY])

    return bi

def index_vec(vec, point):
    n = len(vec) - 1
    if point < vec[0]:
        return 0
    elif point > vec[n]:
        return n-2
    else:
        for i in range(n):
            if vec[i] <= point and point <= vec[i+1]:
                return i

#def graf()


import numpy as np
import matplotlib.pyplot as plt
import subprocess

def backtrace(backpointers, node, involved):
    optimal = False
    for P in backpointers[node]:
        if backtrace(backpointers, (P[0], P[1]), involved):
            P[2] = True
            optimal = True
            involved[node[0], node[1]] = 1
    if node[0] == 0 and node[1] == 0:
        print(node)
        return True #Reached the beginning
    return optimal

def LevDist(a, b):
    #Third element in backpointers stores whether this is part
    #of an optimal path
    M = len(a)
    N = len(b)
    D = np.zeros((M+1, N+1))
    D[:, 0] = np.arange(M+1)
    D[0, :] = np.arange(N+1)
    backpointers = {}
    for i in range(0, M+1):
        for j in range(0, N+1):
            backpointers[(i, j)] = []
    for i in range(0, M):
        backpointers[(i+1, 0)].append([i, 0, False])
    for j in range(0, N):
        backpointers[(0, j+1)].append([0, j, False])
    for i in range(1, M+1):
        for j in range(1, N+1):
            delt = 1
            if a[i-1] == b[j-1]:
                delt = 0
            dul = delt + D[i-1, j-1]
            dl = 1 + D[i, j-1]
            du = 1 + D[i-1, j]
            D[i, j] = min(min(dul, dl), du)
            if dul == D[i, j]:
                backpointers[(i, j)].append([i-1, j-1, False])
            if dl == D[i, j]:
                backpointers[(i, j)].append([i, j-1, False])
            if du == D[i, j]:
                backpointers[(i, j)].append([i-1, j, False])
    involved = np.zeros((M+1, N+1))
    backtrace(backpointers, (M, N), involved) #Recursive backtrace from the end
    return (D, backpointers)

def writeChar(fout, i, j, c, bold = False):
    if bold:
        fout.write("\\node at (%g, %g) {\\textbf{%s}};\n"%(j+0.5, i+0.5, c))
    else:
        fout.write("\\node at (%g, %g) {%s};\n"%(j+0.5, i+0.5, c))

def drawPointers(fout, backpointers, M, N):
    for idx in backpointers:
        for P in backpointers[idx]:
            color = 'black'
            if P[2]:
                color = 'red'
            s = [idx[1]+1.4, M-idx[0]+0.8]
            if idx[0]-P[0] == 0: #Left arrow
                fout.write("\\draw [thick, ->, %s] (%g, %g) -- (%g, %g);\n"%(color, s[0], s[1], P[1]+1.6, s[1]))
            elif idx[1]-P[1] == 0: #Up arrow
                fout.write("\\draw [thick, ->, %s] (%g, %g) -- (%g, %g);\n"%(color, s[0], s[1], s[0], M-P[0]+0.2))
            else: #Diagonal Arrow
                fout.write("\\draw [thick, ->, %s] (%g, %g) -- (%g, %g);\n"%(color, s[0], s[1], P[1]+1.6, M-P[0]+0.2))

def drawAllPointers(fout, M, N):
    for i in range(1, M+1):
        for j in range(1, N+1):
            color = 'black'
            s = [j+1.4, M-i+0.8]
            #Left arrow
            fout.write("\\draw [thick, ->, %s] (%g, %g) -- (%g, %g);\n"%(color, s[0], s[1], j-1+1.6, s[1]))
            #Up arrow
            fout.write("\\draw [thick, ->, %s] (%g, %g) -- (%g, %g);\n"%(color, s[0], s[1], s[0], M-(i-1)+0.2))
            #Diagonal Arrow
            fout.write("\\draw [thick, ->, %s] (%g, %g) -- (%g, %g);\n"%(color, s[0], s[1], j-1+1.6, M-(i-1)+0.2))

def LevenshteinExample(a, b, doPointers = False, doAllPointers = False):
    #Make Levenshtein Example
    M = len(a)
    N = len(b)
    (D, backpointers) = LevDist(a, b)
    fout = open("levfig.tex", "w")
    fout.write("\\documentclass[12pt,oneside,a4paper]{article}")
    fout.write("\\usepackage{tikz}")
    fout.write("\\begin{document}")
    fout.write("\\begin{tikzpicture}")
    fout.write("\\draw [help lines] (0, 0) grid (%i,%i);\n"%(N+2, M+2))

    writeChar(fout, M, 0, '\\_')
    for i in range(M):
        writeChar(fout, M-(i+1), 0, a[i])

    writeChar(fout, M+1, 1, '\\_')
    for j in range(N):
        writeChar(fout, M+1, j+2, b[j])

    for i in range(M+1):
        for j in range(N+1):
            if i == M and j == N:
                continue
            writeChar(fout, M-i, j+1, int(D[i, j]))
    writeChar(fout, 0, N+1, int(D[-1, -1]))

    if doPointers:
        drawPointers(fout, backpointers, M, N)
    elif doAllPointers:
        drawAllPointers(fout, M, N)
    fout.write("\\end{tikzpicture}")
    fout.write("\\end{document}")
    fout.close()

    subprocess.call(["pdflatex", "levfig.tex"])

if __name__ == '__main__':
    LevenshteinExample("school", "fools", doPointers=False)
    #s = "razmataz"
    #s2 = s[::-1]
    #LevenshteinExample(s, s2, doPointers=True)


'''

The eccentricity of a node v is the maximum distance from v to all other nodes in G.
'''

import networkx as nx

def calculate(network):
    try:
        n = nx.eccentricity(network)
    except:
        return 0

    if len(n.values()) == 0:
        return 0
    else:
        return round(sum(n.values())/len(n.values()), 7)


# @Author: yican.kz
# @Date: 2019-08-23 23:38:16
# @Last Modified by:   yican.kz
# @Last Modified time: 2019-08-23 23:38:16

# Standard libraries
import os
import sys
import random

# Third party libraries
import pandas as pd
import numpy as np
from numba import jit

sys.path.insert(0, os.path.abspath(".."))
from tabular_buddy.utils import parallelize, Timer

def monte_carlo_pi(nsamples):
    acc = 0
    for i in range(nsamples):
        x = random.random()
        y = random.random()
        if (x ** 2 + y ** 2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples

@jit(nopython=True)
def monte_carlo_pi_numba(nsamples):
    acc = 0
    for i in range(nsamples):
        x = random.random()
        y = random.random()
        if (x ** 2 + y ** 2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples

def func(data):
    return data["a"].apply(lambda x: np.sqrt(x)) - data["b"] ** 2

if __name__ == "__main__":
    # ==============================================================================================================
    # Using numba to speed up your function, [21 times!]
    # -----------------------------------------
    # Before speed up : Res is 3.1414 | 21 seconds
    # After speed up  : Res is 3.1419 | 1 seconds
    # ==============================================================================================================
    nsamples = 10000000
    data = pd.DataFrame({"a": np.random.rand(nsamples), "b": np.random.rand(nsamples)})

    tick_tock = Timer()
    print("Before speed up : Res is {:.4f} | {} seconds".format(monte_carlo_pi(50000000), int(tick_tock())))
    print("After speed up  : Res is {:.4f} | {} seconds".format(monte_carlo_pi_numba(50000000), int(tick_tock())))

    # ==============================================================================================================
    # Using python multiprocessing to speed up your function, [3 times!]
    # Actual performance depends on your machine
    # -----------------------------------------
    # Before speed up : Res 3333598.7172 | 16 seconds
    # After speed up  : Res 3333598.7172 | 5 seconds
    # ==============================================================================================================
    res = func(data)
    print("Before speed up : Res {:.4f} | {} seconds".format(res.sum(), int(tick_tock())))
    res = parallelize(data, func)
    print("After speed up  : Res {:.4f} | {} seconds".format(res.sum(), int(tick_tock())))