import numpy as np
import sympy as sy

def solveeq_gauss(a, b, x):
    """
    solve equation ax=b using simple gauss method
    """
    # define extended coefficient matrix as 'a'
    a = np.concatenate([a, b], axis=1)
    # push forward
    (row, col) = a.shape
    for j in range(row):
        print("a=\n", a)
        for i in range(j+1, row):
            p = a[i, j] / a[j, j]
            a[i] -= p*a[j]
    # back forward
    for i in range(row)[::-1]:  # reverse iteration
        x[i] = (a[i, col-1]-np.dot(a[i, i+1:row], x[i+1:row]))/a[i, i]

def confirm_numpy(a, b, x):
    x = np.linalg.inv(a)@b

def confirm_sympy(a, b, x):
    a = sy.Matrix(a)
    x = a.solve(b)

def verify(a, b, x):
    solveeq_gauss(a, b, x)
    print("gauss x:\n{}".format(x))
    confirm_numpy(a, b, x)
    print("numpy x:\n{}".format(x))
    confirm_sympy(a, b, x)
    print("sympy.solve x:\n{}".format(x))

def test1():
    a = np.matrix([[2, 4, 6],
                   [1, -1, 5],
                   [4, 1, -2]], dtype='float64')
    b = np.matrix([28, 7, 21], 'float64').T
    x = np.empty(b.shape)
    verify(a, b, x)

def test2():

    a = np.matrix([[2, 4, 5, 2],
                   [1, -8, 2, -6],
                   [4, 1, -10, -2],
                   [1, 7, 1, -2]], dtype='float64')
    b = np.matrix([9, -3, 1, -3], 'float64').T
    x = np.empty(b.shape)
    verify(a, b, x)

def main():
    test1()
    test2()

if __name__ == '__main__':
    main()


"""
Locally Weighted Linear Regression
"""

import numpy as np

class Locally_Weighted_Linear_Regression():
    """Iterative version of Locally Weighted Linear Regression """

    def fit(self, X, y, X_est, tau=1):
        """
        Fit X_est with Locally weighted linear regression according to X and y

        Parameters
        ----------
        X : {array-like, sparse matrix}, shape = [n_samples, n_features]
            Training vectors, where n_samples is the number of samples and
            n_features is the number of features.

        y : array-like, shape = [n_samples]
            Target values.

        X_est : {array-like, sparse matrix}, shape = [n_samples, n_features]
            Estimation vectors, where n_samples is the number of samples and
            n_features is the number of features.

        tau : float, tau >= 0
            The the bandwidth parameter tau controls how quickly the weight of a training example falls off with distance of its x(i) from the query point x.

        References
        ----------
        CS229 Lecture notes1, Chapter 3 Locally weighted linear regression, Prof. Andrew Ng
        http://cs229.stanford.edu/notes/cs229-notes1.pdf
        """

        thetas = []  # init estimated thetas
        estimation = []  # init estimation of X_est

        if tau <= 0:
            print "tau should be greater than 0."
            return [], []

        for x in X_est:  # for every sample in X_est
            # calculate weights that depend on the particular vector x
            weights = np.exp((-(X - x) * (X - x)).sum(axis=1) / (2 * tau ** 2))
            W = np.diag(weights)  # diagonal matrix with weights
            x_W = np.dot(X.T, W)
            A = np.dot(x_W, X)
            b = np.dot(x_W, y)
            thetas.append(np.linalg.lstsq(A, b)[0])  # calculate thetas for given x with: A^-1 * b
            estimation.append(np.dot(x, thetas[-1]))  # calculate estimation for given x and thetas

        return thetas, estimation

if __name__ == '__main__':
    """
    Example was taken from:
    http://www.dsplog.com/2012/02/05/weighted-least-squares-and-locally-weighted-linear-regression/
    """
    import datasets
    import matplotlib.pyplot as plt

    X, y = datasets.regression_data()

    lwlr = Locally_Weighted_Linear_Regression()
    taus = [1, 10, 25]
    plt.scatter(X[:, 1], y)  # Plot train data

    color = ["r", "g", "b"]
    for i, tau in enumerate(taus):
        thetas, estimation = lwlr.fit(X, y, X, tau=tau)
        plt.plot(X[:, 1], estimation, c=color[i])  # Plot prediction

    plt.show()


import numpy as np

def l_norm(l_v):
    """Computes the norm of a list of numpy array.

    Parameters
    ----------
    l_v : array-like

    Returns
    -------
    float
        The norm of l_v.
    """
    return np.sqrt(np.sum([np.sum(np.square(l_v[i])) for i in range(0, len(l_v))]))

def l_scalar_product(l_v, l_w):
    """Computes the scalar product between two list of numpy array.

    Parameters
    ----------
    l_v : array-like
    l_w : array-like

    Returns
    -------
    float
        The scalar product between l_v and l_w.

    """

    return np.sum([np.sum(np.multiply(l_v[i], l_w[i])) for i in range(0, len(l_v))])

def make_vector(l_v):
    """Takes a list of numpy array and returns a column vector.

    Parameters
    ----------
    l_v : array-like

    Returns
    -------
    array
        Array with dimensions (n, 1).
    """

    row_vector = [l_v[l].flatten() for l in range(len(l_v))]
    return np.concatenate(row_vector).reshape(-1, 1)

def restore_w_to_model(model, w):
    """Takes an array of weights and transforms it into a list
       of matrices with dimensions taken from the model passed.

    Parameters
    ----------
    model : isanet.model.MLP
            The Multilayer Perceptron object.
    w : array

    Returns
    -------
        array-like
    """
    start = 0
    weights = [0]*model.n_layers
    for i in range(model.n_layers):
        n_rows = model.weights[i].shape[0]
        n_cols = model.weights[i].shape[1]
        end = n_rows*n_cols
        weights[i] = w[start:start + end].reshape(n_rows,n_cols)
        start = start + end
    return weights


import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
import seaborn as sns

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

# Implementation of the stochastic gradient descent
def gradient_descent(X, y, params, learning_rate, iterations):
    m = len(y)
    cost_history = np.zeros((iterations, 1))

    for i in range(iterations):
        params = params - (learning_rate/m) * (X.T @ (sigmoid(X @ params) - y))
        cost_history[i] = compute_cost(X, y, params)

    return (cost_history, params)

# Computing the cost via the negative log loss
def compute_cost(X, y, theta):
    m = len(y)
    h = sigmoid(X @ theta)
    epsilon = 1e-5
    cost = (1/m)*(((-y).T @ np.log(h + epsilon))-((1-y).T @ np.log(1-h + epsilon)))
    return cost

# Implementation of the stochastic gradient descent with regularization
def gradient_descent_rg(X, y, params, learning_rate, iterations, lmb):
    m = len(y)
    cost_history = np.zeros((iterations, 1))

    for i in range(iterations):
        params = params - ((learning_rate/m) * (X.T @ (sigmoid(X @ params) - y))  + (lmb/m)*params)
        cost_history[i] = compute_cost(X, y, params)

    return (cost_history, params)

# Computing the cost via the negative log loss with regularization
def compute_cost_rg(X, y, theta, lmb):
    m = len(y)
    h = sigmoid(X @ theta)
    epsilon = 1e-5
    cost = (1/m)*(((-y).T @ np.log(h + epsilon))-((1-y).T @ np.log(1-h + epsilon))) + (lmb/2)*np.sum(np.square(theta))
    return cost

def predict(X, params):
    return np.round(sigmoid(X @ params))

# Driver Code

# Used scikit-learn to make a random binary classification dataset
X, y = make_classification(n_samples=500, n_features=2, n_redundant=0, n_informative=1,
                             n_clusters_per_class=1, random_state=14)

y = y[:, np.newaxis]

sns.set_style('white')
sns.scatterplot(X[:, 0], X[:, 1], hue=y.reshape(-1))

m = len(y)

X = np.hstack((np.ones((m, 1)), X))
n = np.size(X, 1)
params = np.zeros((n, 1))

iterations = 1000  # Number of iterations
learning_rate = 0.01  # Learning rate of stochastic gradient descent

initial_cost = compute_cost(X, y, params)

print("Initial Cost is: {} \n".format(initial_cost))
# without reg
(cost_history, params_optimal) = gradient_descent(X, y, params, learning_rate, iterations)
# with reg
(cost_history_rg, params_optimal_rg) = gradient_descent_rg(X, y, params, learning_rate, iterations, lmb=0.01)

print("Optimal Parameters without regularization are: \n", params_optimal, "\n")
print("Optimal Parameters with regularization are: \n", params_optimal_rg, "\n")

plt.figure()
sns.set_style('white')
plt.plot(range(len(cost_history)), cost_history)
plt.plot(range(len(cost_history_rg)), cost_history_rg)
plt.legend(['without_reg', 'with_reg'])
plt.title("Convergence Graph of Cost Function")
plt.xlabel("Number of Iterations")
plt.ylabel("Cost")
plt.show()

# predictions without regularization
y_pred = predict(X, params_optimal)
score = float(sum(y_pred == y)) / float(len(y))
print("Score without regularization is : ", score)

# predictions with regularization
y_pred = predict(X, params_optimal_rg)
score = float(sum(y_pred == y)) / float(len(y))
print("Score with regularization is : ", score)

slope = -(params_optimal[1] / params_optimal[2])
intercept = -(params_optimal[0] / params_optimal[2])

sns.set_style('white')
sns.scatterplot(X[:, 1], X[:, 2], hue=y.reshape(-1))

ax = plt.gca()
ax.autoscale(False)
x_vals = np.array(ax.get_xlim())
y_vals = intercept + (slope * x_vals)
plt.title('Division Proposed by our Trained Model')
plt.plot(x_vals, y_vals, c="k")
plt.show()


from sympy import *

x = symbols('x[0]')
y = symbols('x[1]')
z = symbols('x[2]')
uu = sin(x)*exp(x+y+z)
uv = sin(y)*exp(x+y+z)
uw = sin(z)*exp(x+y+z)
uu = (x**4+y**5+z**6)
uv = (x**4+y**5+z**6)
uw =(x**4+y**5+z**6)
u = diff(uw,y)-diff(uv,z)
v = diff(uu,z)-diff(uw,x)
w = diff(uv,x)-diff(uu,y)
p = sin(y)+exp(x+y+z)
# u = y**1*z**2
# v = x**1*z**2
w = diff(u,x)
# p = x*y
print "u:    ",u
print "\n"
print "v:    ",v
print "\n"
print "w:    ",w
print "\n"
print "p:    ",p
print "\n"
L1 = diff(u,x,x)+diff(u,y,y) + diff(u,z,z)
L2 = diff(v,x,x)+diff(v,y,y) + diff(v,z,z)
L3 = diff(w,x,x)+diff(w,y,y) + diff(w,z,z)
print "Laplacian 1:    ",L1
print "\n"
print "Laplacian 2:    ",L2
print "\n"
print "Laplacian 3:    ",L3
print "\n"
A1 = u*diff(u,x)+v*diff(u,y)+w*diff(u,z)
A2 = u*diff(v,x)+v*diff(v,y)+w*diff(v,z)
A3 = u*diff(w,x)+v*diff(w,y)+w*diff(w,z)

print "Advection 1:    ",A1
print "\n"
print "Advection 2:    ",A2
print "\n"
print "Advection 3:    ",A3
print "\n"
P1 = diff(p,x)
P2 = diff(p,y)
P3 = diff(p,z)
print "Pressure 1:    ",P1
print "\n"
print "Pressure 2:    ",P2
print "\n"
print "Pressure 3:    ",P3

from dolfin import Expression
u0 = Expression((ccode(u),ccode(v),ccode(w)))

Laplacian = Expression((ccode(L1),ccode(L2),ccode(L3)))
Advection = Expression((ccode(A1),ccode(A2),ccode(A3)))
gradPres = Expression((ccode(P1),ccode(P2),ccode(P3)))

 # (exp(x + y)*sin(y) + exp(x + y)*cos(y))**2 - 2*exp(2*x + 2*y)*sin(y)*cos(y)


import numpy as np

def min_max_scaler(X):
    '''
    for each feature x in X, normalize the data points to fit between 0 and 1, using this formula:

    (x - min(X)) / (max(X) - min(X))

    intuitively, we see that the min feature will be assigned a new value of 0 because
    when x is the min feature the numerator of the expression will be 0. and when x
    is the max value for a feature it will be replaced with 1 because the numerator and
    the denominator will be the same.

    :param (np array) X: a numpy array of numpy arrays, where each nested
    array represents a feature vector.

    :returns (np array): a numpy array of numpy arrays, where each nested array
    of feature vectors has been scaled to a number between 0 and 1.
    '''
    # take the transpose of X to get each feature column as its own row
    all_features = X.T
    all_scaled_features = []
    for features in all_features:
        feature_min = features.min()
        feature_max = features.max()
        # apply min max to the features vector
        scaled_features = (features - feature_min) / float(feature_max - feature_min)
        all_scaled_features.append(scaled_features)

    # transpose again to return the features to columns rather than rows
    scaled_X = np.array(all_scaled_features).T
    return scaled_X


# -*- coding: utf-8 -*-
"""
Created on Wed May  2 12:08:46 2018

@author: User
"""
import numpy as np

def weighted_median(values, weights):
    ''' compute the weighted median of values list. The
weighted median is computed as follows:
    1- sort both lists (values and weights) based on values.
    2- select the 0.5 point from the weights and return the corresponding values as results
    e.g. values = [1, 3, 0] and weights=[0.1, 0.3, 0.6] assuming weights are probabilities.
    sorted values = [0, 1, 3] and corresponding sorted weights = [0.6,     0.1, 0.3] the 0.5 point on
    weight corresponds to the first item which is 0. so the weighted     median is 0.'''

    #convert the weights into probabilities
    sum_weights = sum(weights)
    weights = np.array([(w*1.0)/sum_weights for w in weights])
    #sort values and weights based on values
    values = np.array(values)
    sorted_indices = np.argsort(values)
    values_sorted  = values[sorted_indices]
    weights_sorted = weights[sorted_indices]
    #select the median point
    it = np.nditer(weights_sorted, flags=['f_index'])
    accumulative_probability = 0
    median_index = -1
    while not it.finished:
        accumulative_probability += it[0]
        if accumulative_probability > 0.5:
            median_index = it.index
            return values_sorted[median_index]
        elif accumulative_probability == 0.5:
            median_index = it.index
            it.iternext()
            next_median_index = it.index
            return np.mean(values_sorted[[median_index, next_median_index]])
        it.iternext()

    return values_sorted[median_index]


import numpy as np

profits = [1, 2, 5, 6]
weights = [2, 3, 4, 5]
sackSize = 8

def fractional_knapsack_greedy(value, weight, capacity):
    index = list(range(len(value)))

    ratio = [v/w for v, w in zip(value, weight)]

    index.sort(key=lambda i: ratio[i], reverse=True)  # n log n

    max_value = 0
    fractions = [0]*len(value)
    for i in index:
        if weight[i] <= capacity:
            fractions[i] = 1
            max_value += value[i]
            capacity -= weight[i]
        else:
            fractions[i] = capacity/weight[i]
            max_value += value[i]*capacity/weight[i]
            break

    return max_value, fractions

a = fractional_knapsack_greedy(profits, weights, sackSize)
print("Greedy : ", a[0], list(zip(a[1], profits)))


import numpy as np
from seizures.features.FeatureExtractBase import FeatureExtractBase
from seizures.data.Instance import Instance
import numpy as np
from numpy import unwrap, angle
from scipy.signal import hilbert
from matplotlib import pyplot as plt

class PLVFeatures(FeatureExtractBase):
    """
    Class to extracts Phase Locking Value (PLV) between pairs of channels.
    @author V&J
    """

    def __init__(self):
        pass

    def extract(self, instance):
        data = instance.eeg_data
        n_ch, time = data.shape
        n_pairs = n_ch*(n_ch-1)/2
        # initiate matrices
        phases = np.zeros((n_ch,time))
        delta_phase_pairwise = np.zeros((n_pairs,time))
        plv = np.zeros((n_pairs,))

        # extract phases for each channel
        for c in range(n_ch):
            phases[c,:] = unwrap(angle(hilbert(data[c,:])))

        # compute phase differences
        k = 0
        for i in range(n_ch):
            for j in range(i+1,n_ch):
                delta_phase_pairwise[k,:] = phases[i,:]-phases[j,:]
                k+=1

        # compute PLV
        for k in range(n_pairs):
            plv[k] = np.abs(np.sum(np.exp(1j*delta_phase_pairwise[k,:]))/time)

        self.assert_features(plv)
        # features = a 1d ndarray
        return plv

    def __str__(self):
        return "PLV"


import matplotlib.pyplot as plt
from numpy.ma import arange

from probability.distributions.continuous.normal import Normal
from mpl_format.axes.axis_utils import new_axes

x = arange(-5, 5.05, 0.05)

def plot_wikipedia_pdfs():
    """
    https://en.wikipedia.org/wiki/Normal_distribution#/media/
    File:Normal_Distribution_PDF.svg
    """
    ax = new_axes()
    Normal(mu=0, sigma_sq=0.2).plot(x=x, color='blue', ax=ax)
    Normal(mu=0, sigma_sq=1.0).plot(x=x, color='red', ax=ax)
    Normal(mu=0, sigma_sq=5.0).plot(x=x, color='orange', ax=ax)
    Normal(mu=-2, sigma_sq=0.5).plot(x=x, color='green', ax=ax)
    ax.legend(loc='upper right')
    plt.show()

def plot_wikipedia_cdfs():
    """
    https://en.wikipedia.org/wiki/Normal_distribution#/media/
    File:Normal_Distribution_CDF.svg
    """
    ax = new_axes()
    Normal(mu=0, sigma_sq=0.2).cdf().plot(x=x, color='blue', ax=ax)
    Normal(mu=0, sigma_sq=1.0).cdf().plot(x=x, color='red', ax=ax)
    Normal(mu=0, sigma_sq=5.0).cdf().plot(x=x, color='orange', ax=ax)
    Normal(mu=-2, sigma_sq=0.5).cdf().plot(x=x, color='green', ax=ax)
    ax.legend(loc='upper left')
    plt.show()

if __name__ == '__main__':

    plot_wikipedia_pdfs()
    plot_wikipedia_cdfs()


# coding: utf-8

# # Function mosaic

# ## Synopse
#
# Create a 2D visualization of a 3D image.
#
# - **g = mosaic(f,N,s)**
#
#   - **g**: Image. Mosaic of 2D images.
#
#   - **f**: Image. 3D image.
#   - **N**: Integer. Number of image columns in mosaic.
#   - **s**: Float. Default: 1.0. Scale factor.

# ## Description
#
# This function puts the slices of a 3D image side-by-side on a mosaic (2D image). The objective is to provide basic 3D visualization.

# In[8]:

import numpy as np
import scipy.misc

def mosaic(f,N,s=1.0):
    f = np.asarray(f)
    d,h,w = f.shape
    N = int(N)
    nLines = int(np.ceil(float(d)/N))
    nCells = int(nLines*N)

    # Add black slices to match the exact number of mosaic cells
    fullf = np.resize(f, (nCells,h,w))
    fullf[d:nCells,:,:] = 0

    Y,X = np.indices((nLines*h,N*w))
    Pts = np.array([
            (np.floor(Y/h)*N + np.floor(X/w)).ravel(),
            np.mod(Y,h).ravel(),
            np.mod(X,w).ravel() ]).astype(int).reshape((3,int(nLines*h),int(N*w)))
    g = fullf[Pts[0],Pts[1],Pts[2]]
    if (s != 1.0):
        #g = scipy.ndimage.interpolation.zoom(g,s,order=5)
        g = scipy.misc.imresize(g,s,interp='bilinear')
    return g

# ## Examples

# In[1]:

testing = (__name__ == "__main__")
if testing:
    get_ipython().system(' jupyter nbconvert --to python mosaic.ipynb')
    import sys
    import os
    import numpy as np
    ea979path = os.path.abspath('../../')
    if ea979path not in sys.path:
        sys.path.append(ea979path)
    import ea979.src as ia

# ### Example 1

# In[4]:

if testing:
    t = np.arange(60)
    t.shape = (5,3,4)

    print('Original 3D image:\n', t, '\n\n')

    for i in range(1,7):
        g = ia.mosaic(t,i)
        print('Mosaic with %d image column(s):\n' % i)
        print(g)
        print()

    for i in range(1,7):
        g = ia.mosaic(t,i,0.8)
        print('Mosaic with %d image column(s):\n' % i)
        print(g)
        print()

# ### Example 2

# In[5]:

if testing:
    d,r,c = np.indices((5,3,4))
    t = ((r + c)%2) >0

    for i in range(1,7):
       g = ia.mosaic(t,i,0.8)
       print('Mosaic with %d image column(s):\n' % i)
       print(g)
       print()

# ## Equation
#
# Each element of the original 3D image $(x_{0},y_{0},z_{0})$ is mapped to an element on the destination 2D image (mosaic) $(x_{d},y_{d})$ by the equations.
#
# $$ \begin{matrix}
#     x_{0} &=& x_{d} &mod& w \\
#     y_{0} &=& y_{d} &mod& h \\
#     z_{0} &=& \lfloor \frac{y_{d}}{h} \rfloor N + \lfloor \frac{x_{d}}{w} \rfloor
#     \end{matrix} $$
#
# were $N$ is the number of image columns in mosaic, $w$ is the original image width and $h$ is the original image height.

# In[ ]:


import numpy as np
from scipy.stats import norm

def black_scholes(t=40, r=4.00, v=32.00, K=60, St=62, type='c'):
    """
    Parameters:
    K : Excercise Price
    St: Current Stock Price
    v : Volatility in percentage
    r : Risk free rate in percentage
    t : Time to expiration in days
    type: Type of option 'c' for call 'p' for put
    default: 'c'
    """
    # if type = 'c' or 'C' call option else put option
    try:
        type=type.lower()
        if(type=='c'):
            option_type='call'
        else:
            option_type='put'
    except:
        option_type='put'

    #Check time
    try:
        #convert time in days to years
        t=t/365
    except:
        raise TypeError("Enter numerical value for time")

    #Check risk free rate
    try:
        #convert percentage to decimal
        r=r/100
    except:
        raise TypeError("Enter numerical value for risk free rate")

    #Check volatility
    try:
        #convert percentage to decimal
        v=v/100
    except:
        raise TypeError("Enter numerical value for volatility")

    #Check Stock Price
    try:
        St=St+0
    except:
        raise TypeError("Enter numerical value for stock price")

    #Check Exercise Price
    try:
        K=K+0
    except:
        raise TypeError("Enter numerical value for Exercise price")

    n1=np.log(St/K)
    n2=(r+(np.power(v,2)/2))*t
    d=v*(np.sqrt(t))

    d1=(n1+n2)/d
    d2=d1-(v*np.sqrt(t))

    if type=='c':
        N_d1=norm.cdf(d1)
        N_d2=norm.cdf(d2)
    else:
        N_d1=norm.cdf(-d1)
        N_d2=norm.cdf(-d2)

    A=(St*N_d1)
    B=(K*N_d2*(np.exp(-r*t)))

    if type=='c':
        val=A-B
        val_int=max(0,St-K)
    else:
        val=B-A
        val_int=max(0,K-St)
    val_time=val-val_int

    # Option values in dictionary
    value={'option value':val, 'intrinsic value':val_int, 'time value':val_time}

    #CALCULATE OPTION GREEKS
    if type=='c':
        delta=N_d1
        theta=(-((St*v*np.exp(-np.power(d1,2)/2))/(np.sqrt(8*np.pi*t)))-(N_d2*r*K*np.exp(-r*t)))/365
        rho=t*K*N_d2*np.exp(-r*t)/100
    else:
        delta=-N_d1
        theta=(-((St*v*np.exp(-np.power(d1,2)/2))/(np.sqrt(8*np.pi*t)))+(N_d2*r*K*np.exp(-r*t)))/365
        rho=-t*K*N_d2*np.exp(-r*t)/100

    gamma=(np.exp(-np.power(d1,2)/2))/(St*v*np.sqrt(2*np.pi*t))
    vega=(St*np.sqrt(t)*np.exp(-np.power(d1,2)/2))/(np.sqrt(2*np.pi)*100)

    #Option greeks in Dictionary
    greeks={'delta':delta, 'gamma':gamma, 'theta':theta, 'vega':vega, 'rho':rho}

    return {'value':value, 'greeks':greeks}


import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

iris_df = pd.read_csv('https://archive.ics.uci.edu/ml/'
        'machine-learning-databases/iris/iris.data', header=None)

print(iris_df.head())
print(iris_df.tail())

X = iris_df.iloc[:,0:4].values
y = iris_df.iloc[:,4].values

X_train, X_test, y_train, y_test =  train_test_split(X, y, test_size=0.3, random_state=0)

sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)
X_test_std = sc.transform(X_test)

mean_vec = np.mean(X_train_std, axis=0)
cov_mat = (X_train_std - mean_vec).T.dot((X_train_std - mean_vec)) / (X_train_std.shape[0]-1)
print('Covariance matrix \n%s' %cov_mat)
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)

print('\nEigenvalues \n%s' % eigen_vals)
tot = sum(eigen_vals)
var_exp = [(i / tot) for i in sorted(eigen_vals, reverse=True)]
cum_var_exp = np.cumsum(var_exp)

plt.bar(range(0, 4), var_exp, alpha=0.5, align='center',
        label='individual explained variance')
plt.step(range(0, 4), cum_var_exp, where='mid',
         label='cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal components')
plt.legend(loc='best')
plt.tight_layout()
plt.show()

eigen_pairs = [(np.abs(eigen_vals[i]), eigen_vecs[:, i])
               for i in range(len(eigen_vals))]
print(eigen_pairs)

W = np.hstack((eigen_pairs[0][1][:, np.newaxis],
               eigen_pairs[1][1][:, np.newaxis], eigen_pairs[2][1][:, np.newaxis]))
print('Matrix W:\n', W)

print("X_train_std[0].dot(W) ",X_train_std[0].dot(W))

X_train_pca = X_train_std.dot(W)
print("X_train_pca ",X_train_pca)

colors = ['r', 'b', 'g']
markers = ['s', 'x', 'o']

for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_pca[y_train == l, 0],
                X_train_pca[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()

for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_pca[y_train == l, 1],
                X_train_pca[y_train == l, 2],
                c=c, label=l, marker=m)
plt.xlabel('PC 2')
plt.ylabel('PC 3')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()

for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_pca[y_train == l, 0],
                X_train_pca[y_train == l, 2],
                c=c, label=l, marker=m)
plt.xlabel('PC 1')
plt.ylabel('PC 3')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()

from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(1, figsize=(8, 6))
ax = Axes3D(fig, elev=-150, azim=110)
for l, c, m in zip(np.unique(y_train), colors, markers):
    ax.scatter(X_train_pca[y_train == l, 0], X_train_pca[y_train == l, 1], X_train_pca[y_train == l, 2],c=c,  edgecolor='k', s=40)
ax.set_title("First three PCA directions")
ax.set_xlabel("1st eigenvector")
ax.w_xaxis.set_ticklabels([])
ax.set_ylabel("2nd eigenvector")
ax.w_yaxis.set_ticklabels([])
ax.set_zlabel("3rd eigenvector")
ax.w_zaxis.set_ticklabels([])

plt.show()


# -*- coding: utf-8 -*-
"""
Created on Sun May 23 02:08:50 2021

@author: Akshay Prakash
"""

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
df = pd.read_csv(r'\96-20PL.csv')
df1 = pd.read_csv(r'\F2021PL.csv')
newframe = [df, df1]
NewResult = pd.concat(newframe)

print(df['Pts'])
y = df.Rk
x = df.Pts.values.reshape(-1,1) #.values with reshape because its DF
print(x.shape, y.shape) #(rows,colums)
model = LinearRegression().fit(x, y)
r_sqrt = model.score(x,y)
print('r_sqrt: ', r_sqrt)
intercept = model.intercept_
slope = model.coef_
print('intercept: ', intercept)
print('slope: ', slope)
y_predicted = intercept + slope* x #Linear Regression eqn

fig, ax = plt.subplots(figsize = (10,10))
plt.scatter(x,y, color = 'blue', label = 'Points')
plt.plot(x,y_predicted, color = 'red', label = 'Linear Regression')
plt.legend()
plt.ylim(0.5, 20.5)
plt.yticks([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20])
plt.gca().invert_yaxis()#rewrite because it should be from low to high
plt.xlabel('Points')
plt.ylabel('League Position (Rank)')
plt.title('Evaluating relationship between Points and league position')
plt.text(10, 3, "Slope: -0.332 ", color = 'red', size= '15')
plt.text(10, 3.7, "Intercept: 27.80 ", color = 'red', size= '15')
plt.text(10, 4.4, "r_sqrt: 0.89 ", color = 'red', size= '15')
plt.text(92, 1.4, "Man City 18/19 (98)", color ='red', size = '7')
plt.text(92, 2.4, "Liverpool 18/19 (97)", color ='red', size = '7')
plt.text(7, 19.6, "Derby County 2008/09 ", color = 'red', size= '7')

y = int(input("Enter a number: "))
x = (y- intercept)/slope
print(x)


import numpy as np
class NoiseGenerator:
    @staticmethod
    def power_law_noise(N,alpha,A_rms):
        """
            Generates a time-domain vector of power law noise samples.

            Parameters
            ----------
            N: int
                Number of samples to be generated from the power-law noise process.
            alpha: int
                Exponent paramter in the power-law noise process S(f)~h*f(-alpha).
            A_rms: float
                Root-mean squared (RMS) ampltiude of the generated time series

            Returns
            -------
            Y: nparray
                N-sample vector of samples from the specified noise process.

            Notes
            -----
        """

        N_nominal = N

        if(N%2 !=0):
            N_nominal = N+1

        halfSpectrumLength = N_nominal/2

        X_t = np.random.normal(0, 1, (N_nominal,1))
        X_omega = np.fft.fft(X_t)

        #shift the FFT to put the 0-frequency in the center
        X_omega = np.fft.fftshift(X_omega)

        #initialize colored noise spectrum
        X_omega_c = np.empty(N_nominal,np.dtype('c16'))

        #color the noise according to the exponent parameter
        #leave the DC component at X_omega[halfSpectrumLength] untouched
        for i in range(halfSpectrumLength+1,N_nominal):
            X_omega_c[i] = np.divide(X_omega[i],np.sqrt((i-halfSpectrumLength)**alpha))
        for i in range(halfSpectrumLength-1,-1,-1):
            X_omega_c[i] = np.divide(X_omega[i],np.sqrt((halfSpectrumLength-i)**alpha))

        #transform from frequency space back into real space
        Y = np.fft.ifft(np.fft.ifftshift(X_omega_c))
        Y = np.real(Y)

        #guarantee A_rms standard deviation and zero mean
        Y = Y - np.mean(Y)
        Y_rms = np.sqrt(np.mean(Y**2))
        Y = A_rms*np.divide(Y,Y_rms)

        return Y


import numpy as np
import math
#a = np.array([5.576091766357422, 8.455062866210938, -0.06940200179815292])
#b = np.array([6.994688034057617, 9.038549423217773, -0.23745399713516235])
#c = np.array([7.181145191192627, 10.318812370300293, -0.020966999232769012])
#d = np.array([8.561141967773438, 10.942671775817871, -0.06447900086641312])
def getNormedVector(a,b):
    return (b-a)/np.linalg.norm(b-a)

def getAngle(a,b):
    return np.rad2deg(np.arccos(np.dot(a/np.linalg.norm(a),b/np.linalg.norm(b))))

def getSign(a,b):
    b = b/np.linalg.norm(b)
    rec = np.dot(a,b)
    return rec

def getdih(a,b,c,d):
    #v1 = np.subtract(a,b)
    #v2 = np.subtract(b,c)
    #v3 = np.subtract(c,d)
    v1 = getNormedVector(a,b)
    v2 = getNormedVector(b,c)
    v3 = getNormedVector(c,d)
    v1v2 = np.cross(v1,v2)
    v2v3 = np.cross(v2,v3)
    d12 = np.dot(v1v2,v1v2)
    d23 = np.dot(v2v3,v2v3)
    rotdir = getSign(v1,-v2v3)
    #print d12, d23
    #print "sign", rotdir
    rec = getAngle(v1v2,v2v3)
    #print rec
    # if the rotdir is positive, the rotation is clockwise
    if rotdir > 0.0:
        #rec = -rec
        rec = 360.0 - rec
    #costheta = float(np.dot(d12,d23))/float((d12*d23))
    #theta = math.acos(costheta)/math.pi*180.0
    #print costheta, theta
    #return theta
    #if rec < 0.0:
    #    rec = 180.0 + rec
    return rec
#theta = getdih(a,b,c,d)
#print theta


from sympy import *
from sympy.physics.vector import *
from sympy.vector import *

R = CoordSys3D('R')
t = Symbol('t')
a = -sin(t)*R.i + cos(t)*R.j + 0*R.k
b = -cos(t)*R.i + -sin(t)*R.j + 0*R.k

# same
print(simplify(dot(a, b)))
print(simplify(a & b))

# same
print(simplify(a ^ b))
print(simplify(cross(a, b)))


import numpy as np

def dist(a, b, axis=1):
    return np.linalg.norm(a - b, axis=axis)

def getRandomCentroids(X, k):
    idx = np.random.choice(np.arange(X.shape[0]), size=k, replace=False)
    return X[idx]

def getLabels(X, centroids):
    labels = []
    for i, p in enumerate(X):
        dists = dist(p, centroids)
        labels.append(np.argmin(dists))
    return labels

def getCentroids(X, labels, k):
    centroids = []
    for i in range(k):
        points = [X[j] for j in range(len(X)) if labels[j] == i]
        centroids.append(np.mean(points, axis=0))
    return centroids

def getError(X, centroids, labels):
    error = 0
    for i, p in enumerate(X):
        error += dist(p, centroids[labels[i]], axis=0)
    return error

def KMeans(X, k, r):
    """
    :param X: data matrix(D * N)
    :param k: desired number of clusters
    :param r: number of repetitions with different random initializations
    :return: centroids: cluster centroids of the data(k * D)
             labels: clustering labels of the data(N)
    """
    X = X.T
    min_error, best_centroids, best_labels = float("inf"), None, None
    for i in range(r):
        labels = np.zeros(len(X))
        centroids = getRandomCentroids(X, k)
        while True:
            old_labels = np.copy(labels)
            labels = getLabels(X, centroids)
            if np.array_equal(labels, old_labels):
                error = getError(X, centroids, labels)
                if error < min_error:
                    min_error, best_centroids, best_labels = error, centroids, labels
                break
            centroids = getCentroids(X, labels, k)
    return best_centroids, best_labels


import numpy as np

class LinearRegression(object):
	def __init__(self, learning_rate = 1e-3):
		self.lr = learning_rate

	def predict(self, X):
		_, dim = X.shape
		if dim > 1:
			## Trying for multiple regression
			return X.dot(self.weights)
		else:
			return X.dot(self.weights) + self.biases

	def cost_function(self, y_pred, y):
		error = np.power(y_pred - y, 2)
		return 1/(2*self.n) * np.sum(error)

	def fit(self, X, y):

		early_stopping = False
		best_loss = np.inf

		self.n, self.feat = X.shape

		self.weights = np.zeros(self.feat)
		self.biases = 0
		self.X = X
		self.y = y

		## Until Convergen
		"""
		while not early_stopping:
			self.pred = self.predict(X)
			loss = self.cost_function(self.pred, y)

			if loss < best_loss:
				best_loss = loss
			else:
				early_stopping = True

			self.update_weights()
		"""

		for _ in range(10000):
			self.pred = self.predict(X)
			loss = self.cost_function(self.pred, y)
			if np.isnan(loss):
				break
			else:
				self.update_weights()

		return self

	def update_weights(self):
		dW = - (2 * (self.X.T.dot(self.y - self.pred))/self.n)
		db = - 2 * np.sum(self.y - self.pred)/self.n

		#dW = self.X.T.dot((self.pred - self.y))/self.n
		#db = np.sum(self.pred - self.y)/self.n

		self.weights = self.weights - self.lr * dW
		self.biases = self.biases - self.lr * db

		return self

class KNeighborsRegression():
	def __init__(self, p = 1, n_neighbors = 2):
		self.k = n_neighbors
		self.p = p

	def distance(self, a, b):
		return np.power(np.sum(np.power(abs( a - b), self.p)), 1/self.p)

	def fit(self, X, y):
		self.X_train = X
		self.y_train = y

	def predict(self, X):
		prediction = np.zeros(X.shape[0])
		for i in range(X.shape[0]):
			row = X[i]
			neighbors = self.get_neighbors(row)
			prediction[i] = np.mean(neighbors)

		return prediction

	def get_neighbors(self, X):
		distances = np.zeros(self.X_train.shape[0])
		for i, row in enumerate(self.X_train):
			distances[i] = self.distance(X, row)

		inds = distances.argsort()
		y_train = self.y_train[inds]
		return y_train[:self.k]

class PCA():
	def __init__(self, n_components):
		self.n_components = n_components
		self.components = None
		self.mean = None

	def fit(self, X):
		self.mean = np.mean(X, axis=0)
		# X -= self.mean
		X_ = X - self.mean
		#sigma = 1/(X.shape[0]-1) * X.T.dot(X)
		sigma = 1/(X.shape[0] - 1) * X_.T.dot(X_)
		U, s, Vt = np.linalg.svd(X_)
		self.components = Vt.T[:, :self.n_components]

	def transform(self, X):
		X_ = X - self.mean
		return X_.dot(self.components)

	def fit_transform(self, X):
		self.fit(X)
		return self.transform(X)


from math import atan2, sqrt, degrees
import numpy as np
from math import radians, sin, cos

RADIUS = 6371.009

def get_centroid(points):
    xy = np.asarray(points)
    xy = np.radians(xy)
    lon, lat = xy[:, 0], xy[:, 1]
    avg_x = np.sum(np.cos(lat) * np.cos(lon)) / xy.shape[0]
    avg_y = np.sum(np.cos(lat) * np.sin(lon)) / xy.shape[0]
    avg_z = np.sum(np.sin(lat)) / xy.shape[0]
    center_lon = atan2(avg_y, avg_x)
    hyp = sqrt(avg_x * avg_x + avg_y * avg_y)
    center_lat = atan2(avg_z, hyp)
    return degrees(center_lon), degrees(center_lat)

def gc_distance_points(a, points):
    b = np.asarray(points)
    lat1, lng1 = radians(a[1]), radians(a[0])
    lat2, lng2 = np.radians(b[:, 1]), np.radians(b[:, 0])

    sin_lat1, cos_lat1 = sin(lat1), cos(lat1)
    sin_lat2, cos_lat2 = np.sin(lat2), np.cos(lat2)

    delta_lng = np.subtract(lng2, lng1)
    cos_delta_lng, sin_delta_lng = np.cos(delta_lng), np.sin(delta_lng)

    d = np.arctan2(np.sqrt((cos_lat2 * sin_delta_lng) ** 2 +
                 (cos_lat1 * sin_lat2 -
                  sin_lat1 * cos_lat2 * cos_delta_lng) ** 2),
                 sin_lat1 * sin_lat2 + cos_lat1 * cos_lat2 * cos_delta_lng)

    return RADIUS * d

def gc_dist(a, b):
        from math import radians, sin, cos, sqrt, atan2
        lat1, lng1 = radians(a[1]), radians(a[0])
        lat2, lng2 = radians(b[1]), radians(b[0])

        sin_lat1, cos_lat1 = sin(lat1), cos(lat1)
        sin_lat2, cos_lat2 = sin(lat2), cos(lat2)

        delta_lng = lng2 - lng1
        cos_delta_lng, sin_delta_lng = cos(delta_lng), sin(delta_lng)

        d = atan2(sqrt((cos_lat2 * sin_delta_lng) ** 2 +
                       (cos_lat1 * sin_lat2 -
                        sin_lat1 * cos_lat2 * cos_delta_lng) ** 2),
                  sin_lat1 * sin_lat2 + cos_lat1 * cos_lat2 * cos_delta_lng)

        return RADIUS * d

if __name__ == '__main__':

    import random
    random.seed(42)
    pts = [(random.uniform(-180, 180), random.uniform(-90, 90)) for _ in range(1000000)]

    centr = get_centroid(pts)

    import cProfile

    cProfile.runctx('gc_distance_points(centr, pts)', globals(), locals())
    cProfile.runctx('dists = [gc_dist(centr, b) for b in pts]', globals(), locals())


import sys
import numpy as np
import matplotlib.pyplot as plt
sys.path.append("..")
from utils import *
from linear_regression import *
from svm import *
from softmax import *
from features import *
from kernel import *

#######################################################################
# Load data
#######################################################################

# Load MNIST data:
train_x, train_y, test_x, test_y = get_MNIST_data()
# Plot the first 20 images of the training set.
# plot_images(train_x[0:20, :])

#######################################################################
# Linear Regression with Closed Form Solution
#######################################################################

def run_linear_regression_on_MNIST(lambda_factor=1):
    """
    Trains linear regression, classifies test data, computes test error on test set

    Returns:
        Final test error
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    train_x_bias = np.hstack([np.ones([train_x.shape[0], 1]), train_x])
    test_x_bias = np.hstack([np.ones([test_x.shape[0], 1]), test_x])
    theta = closed_form(train_x_bias, train_y, lambda_factor)
    test_error = compute_test_error_linear(test_x_bias, test_y, theta)
    return test_error

print('Linear Regression test_error =', run_linear_regression_on_MNIST(lambda_factor=0.01))

#######################################################################
# Support Vector Machine
#######################################################################

def run_svm_one_vs_rest_on_MNIST():
    """
    Trains svm, classifies test data, computes test error on test set

    Returns:
        Test error for the binary svm
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    train_y[train_y != 0] = 1
    test_y[test_y != 0] = 1
    pred_test_y = one_vs_rest_svm(train_x, train_y, test_x)
    test_error = compute_test_error_svm(test_y, pred_test_y)
    return test_error

print('SVM one vs. rest test_error:', run_svm_one_vs_rest_on_MNIST())

def run_multiclass_svm_on_MNIST():
    """
    Trains svm, classifies test data, computes test error on test set

    Returns:
        Test error for the binary svm
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    pred_test_y = multi_class_svm(train_x, train_y, test_x)
    test_error = compute_test_error_svm(test_y, pred_test_y)
    return test_error

print('Multiclass SVM test_error:', run_multiclass_svm_on_MNIST())

#######################################################################
# Multinomial (Softmax) Regression and Gradient Descent
#######################################################################

def run_softmax_on_MNIST(temp_parameter=1):
    """
    Trains softmax, classifies test data, computes test error, and plots cost function

    Runs softmax_regression on the MNIST training set and computes the test error using
    the test set. It uses the following values for parameters:
    alpha = 0.3
    lambda = 1e-4
    num_iterations = 150

    Saves the final theta to ./theta.pkl.gz

    Returns:
        Final test error
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    theta, cost_function_history = softmax_regression(train_x, train_y, temp_parameter, alpha=0.3, lambda_factor=1.0e-4, k=10, num_iterations=150)
    #plot_cost_function_over_time(cost_function_history)
    test_error = compute_test_error(test_x, test_y, theta, temp_parameter)
    # Save the model parameters theta obtained from calling softmax_regression to disk.
    write_pickle_data(theta, "./theta.pkl.gz")

    return test_error

print('softmax test_error=', run_softmax_on_MNIST(temp_parameter=1))

# You can change the temp_parameter = [.5, 1.0, 2.0]

#######################################################################
# Changing Labels
#######################################################################

def run_softmax_on_MNIST_mod3(temp_parameter=1):
    """
    Trains Softmax regression on digit (mod 3) classifications.

    See run_softmax_on_MNIST for more info.
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    train_y_mod3, test_y_mode3 = update_y(train_y, test_y)
    theta, cost_function_history = softmax_regression(train_x, train_y_mod3, temp_parameter, alpha=0.3, lambda_factor=1.0e-4,
                                                      k=10, num_iterations=150)
    #plot_cost_function_over_time(cost_function_history)
    test_error = compute_test_error_mod3(test_x, test_y_mode3, theta, temp_parameter)
    # Save the model parameters theta obtained from calling softmax_regression to disk.
    write_pickle_data(theta, "./theta.pkl.gz")

    return test_error

print('softmax test_error_mod3=', run_softmax_on_MNIST_mod3(temp_parameter=1))

#######################################################################
# Classification Using Manually Crafted Features
#######################################################################

## Dimensionality reduction via PCA ##

n_components = 18
train_x_centered, feature_means = center_data(train_x)
pcs = principal_components(train_x_centered)
train_pca = project_onto_PC(train_x, pcs, n_components, feature_means)
test_pca = project_onto_PC(test_x, pcs, n_components, feature_means)

# train_pca (and test_pca) is a representation of our training (and test) data
# after projecting each example onto the first n_components principal components.

# Train softmax regression model using (train_pca, train_y)
# and evaluate its accuracy on (test_pca, test_y).
temp_parameter = 1
theta, cost_function_history = softmax_regression(train_pca, train_y, temp_parameter, alpha=0.3, lambda_factor=1.0e-4, k=10, num_iterations=150)
#plot_cost_function_over_time(cost_function_history)
test_error = compute_test_error(test_pca, test_y, theta, temp_parameter)
print('After PCA softmax test_error=', test_error)

#       Uncomment to use the plot_PC function in features.py to produce scatterplot
#       of the first 100 MNIST images, as represented in the space spanned by the
#       first 2 principal components found above.
#plot_PC(train_x[range(000, 100), ], pcs, train_y[range(000, 100)], feature_means)#feature_means added since release

#       Uncomment to use the reconstruct_PC function in features.py to show
#       the first and second MNIST images as reconstructed solely from
#       their 18-dimensional principal component representation.
#       Compare the reconstructed images with the originals.
#firstimage_reconstructed = reconstruct_PC(train_pca[0, ], pcs, n_components, train_x, feature_means)#feature_means added since release
#plot_images(firstimage_reconstructed)
#plot_images(train_x[0, ])

#secondimage_reconstructed = reconstruct_PC(train_pca[1, ], pcs, n_components, train_x, feature_means)#feature_means added since release
#plot_images(secondimage_reconstructed)
#plot_images(train_x[1, ])

## Cubic Kernel ##
#Find the 10-dimensional PCA representation of the training and test set
n_components = 10
train_x_centered, feature_means = center_data(train_x)
pcs = principal_components(train_x_centered)
train_pca10 = project_onto_PC(train_x, pcs, n_components, feature_means)
test_pca10 = project_onto_PC(test_x, pcs, n_components, feature_means)

train_cube = cubic_features(train_pca10)
test_cube = cubic_features(test_pca10)
# train_cube (and test_cube) is a representation of our training (and test) data
# after applying the cubic kernel feature mapping to the 10-dimensional PCA representations.

# Train softmax regression model using (train_cube, train_y)
# and evaluate its accuracy on (test_cube, test_y).
temp_parameter = 1
theta, cost_function_history = softmax_regression(train_cube, train_y, temp_parameter, alpha=0.3, lambda_factor=1.0e-4, k=10, num_iterations=150)
#plot_cost_function_over_time(cost_function_history)
test_error = compute_test_error(test_cube, test_y, theta, temp_parameter)
print('After Cubic Kernel softmax test_error=', test_error)


import numpy as np
# http://www.netlib.org/templates/matlab/
nx = 8
A = np.zeros((nx*nx,nx*nx))

def inDomain(i,j):
    if i < 0 or j < 0 or i >= nx  or j >= nx:
        return 0
    return 1
div = np.zeros((nx,nx))
b = np.zeros((nx * nx))
div[3,1] = 1
div[4,1] = 1
for i in range(nx):
    for j in range(nx):
        idx = j * nx + i
        b[idx] = div[i,j]
for k in range(nx*nx):
    A[k,k] = 4
    i = int(k % nx)
    j = int(k / nx)
    if inDomain(i-1, j):
        A[k,k-1] = -1
    if inDomain(i+1, j):
        A[k,k+1] = -1
    if inDomain(i, j-1):
        A[k,k-nx] = -1
    if inDomain(i, j+1):
        A[k,k+nx] = -1
x = np.zeros((nx * nx))
p = np.zeros((nx * nx)) # 方向
res = b - np.dot(A,x) # 残差
bnrm2 = np.linalg.norm(b)
tmax = 100
restld = res.copy()
rho1 = 1
for t in range(0,tmax):
    z = res # 预处理方程组的残差
    rho = np.dot(np.transpose(res),z)
    if rho == 0:
        break
    beta = 0
    if t > 0:
        beta = rho / rho1
    p = z + beta*p
    Ap = np.dot(A,p)
    alpha = rho / np.dot(np.transpose(p),Ap)
    x = x + alpha * p
    res = res - alpha * Ap
    error = np.linalg.norm(res) / bnrm2
    if error < 1e-8:
        break
    rho1 = rho

pre = np.zeros((nx,nx))
for i in range(nx):
    for j in range(nx):
        idx = j * nx + i
        pre[i,j] = x[idx]


try:
    from scipy.special import beta as _beta, betainc as _betainc, gamma as _gamma, digamma as _digamma
    import numpy as _np
    from typing import Union, Dict, List
    from math import sqrt as _sqrt, log as _log
    from univariate._base import SemiInfinite
except Exception as e:
    print(f"some modules are missing {e}")

class F(SemiInfinite):
    """
    This class contains methods concerning the F-distribution [#]_ [#]_ [#]_.

    .. math::
        \\text{F}(x;d_1, d_2) = \\frac{1}{\\text{B}(d_1/2,d_2/2)} \\Big( \\frac{d_1}{d_2} \\Big)^{d_1/2} x^{d_1/2 - 1} \\Big(1 + \\frac{d_1}{d_2}x\\Big) ^{-(d_1+d_2)/2}

    Args:

        df1(int): first degrees of freedom where df1 > 0
        df2(int): second degrees of freedom where df2 > 0
        x(float): random variable where x > 0

    References:
        .. [#] Mood, Alexander; Franklin A. Graybill; Duane C. Boes (1974). Introduction to the Theory of Statistics (Third ed.). McGraw-Hill. pp. 246–249. ISBN 0-07-042864-6.
        .. [#] Weisstein, Eric W. "F-Distribution." From MathWorld--A Wolfram Web Resource. https://mathworld.wolfram.com/F-Distribution.html
        .. [#] NIST SemaTech (n.d.). F-Distribution. Retrived from https://www.itl.nist.gov/div898/handbook/eda/section3/eda3665.htm
    """

    def __init__(self, df1: int, df2: int):
        if (type(df1) is not int) or (df1 <= 0):
            raise TypeError(
                f'degrees of freedom(df) should be a whole number.')
        if (type(df1) is not int) or (df2 <= 0):
            raise TypeError(
                f'degrees of freedom(df) should be a whole number.')

        self.df1 = df1
        self.df2 = df2

    def pdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:
            x (Union[List[float], _np.ndarray, float]): random variable(s)

        Raises:
            ValueError: when there exist a value such that x < 0

        Returns:
            Union[float, _np.ndarray]: evaluation of pdf at x
        """

        def __generator(x: Union[float, _np.ndarray], df1: int, df2: int) -> Union[float, _np.ndarray]:
            x0 = (1 / _beta(df1 / 2, df2 / 2)) * pow(df1 / df2, df1 / 2)
            if type(x) is _np.ndarray:
                return x0 * \
                    _np.power(x, df1 / 2 - 1) * _np.power(1 +
                                                          (df1 / df2) * x, -((df1 + df2) / 2))
            return x0 * \
                pow(x, df1 / 2 - 1) * \
                pow(1 + (df1 / df2) * x, -((df1 + df2) / 2))

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            if _np.any(x < 0):
                raise ValueError(
                    'random variables are expected to be greater than 0.')
            return __generator(x, self.df1, self.df2)

        if x < 0:
            raise ValueError(
                'random variable is expected to be greater than 0.')
        return __generator(x, self.df1, self.df2)

    def cdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:

            x (List[float], numpy.ndarray): random variable or list of random variables

        Returns:
            either cumulative distribution evaluation for some point or plot of F-distribution.
        """
        df1, df2 = self.df1, self.df2

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            return _betainc(df1/2, df2/2, df1*x/(df1*x + df2))

        k = df1*x/(self.df2 + self.df1*x)  # not sure why
        return _betainc(df1/2, df2/2, k)

    def mean(self) -> Union[float, str]:
        """
        Returns: Mean of the F-distribution.
        """
        if self.df2 > 2:
            return self.df2 / (self.df2 - 2)
        return "undefined"

    def mode(self) -> Union[float, str]:
        """
        Returns: Mode of the F-distribution.
        """
        df1 = self.df1
        df2 = self.df2
        if df1 > 2:
            return (df2 * (df1 - 2)) / (df1 * (df2 + 2))
        return "undefined"

    def var(self) -> Union[float, str]:
        """
        Returns: Variance of the F-distribution.
        """
        df1 = self.df1
        df2 = self.df2
        if df2 > 4:
            return (2 * pow(df2, 2) * (df1 + df2 - 2)) / (df1 * (pow(df2 - 2, 2) *
                                                                 (df2 - 4)))
        return "undefined"

    def std(self) -> Union[float, str]:
        """
        Returns: Standard deviation of the F-distribution.
        """
        df1 = self.df1
        df2 = self.df2
        if df2 > 4:
            return _sqrt((2 * pow(df2, 2) * (df1 + df2 - 2))/(df1 * (pow(df2 - 2, 2) * (df2 - 4))))
        return 'undefined'

    def skewness(self) -> Union[float, str]:
        """
        Returns: Skewness of the F-distribution.
        """
        df1 = self.df1
        df2 = self.df2
        if df2 > 6:
            return ((2 * df1 + df2 - 2) * _sqrt(8 * (df2 - 4))) / ((df2 - 6) * _sqrt(df1 * (df1 + df2 - 2)))
        return "undefined"

    def entropy(self) -> Union[float, int]:
        """
        Returns: differential entropy of F-distribution.

        Reference: Lazo, A.V.; Rathie, P. (1978). "On the entropy of continuous probability distributions". IEEE Transactions on Information Theory
        """
        df1 = self.df1
        df2 = self.df2
        return _log(_gamma(df1/2)) + _log(_gamma(df2/2)) -\
            _log(_gamma((df1+df2)/2)) + (1-df1/2)*_digamma(1+df1/2) -\
            (1-df2/2) * _digamma(1+df2/2) + (df1+df2) /\
            2*_digamma((df1+df2)/2) + _log(df1/df2)

    def summary(self) -> Dict[str, Union[float, str]]:
        """
        Returns:
            Dictionary of F distirbution moments. This includes standard deviation.
        """
        return {
            'mean': self.mean(), 'median': self.median(), 'mode': self.mode(),
            'var': self.var(), 'std': self.std(), 'skewness': self.skewness(), 'kurtosis': self.kurtosis()
        }


import numpy as np

import holoviews as hv; hv.extension('bokeh', logo=False)
import panel as pn; pn.extension()

# ============================================================================
def ngon_data( x=0,y=0, specs=[(1,1)], orientation=0., arc=(0,2*np.pi), npoints=3 ):
    ''' compute coordinate locations for n-gons with vertices on an ellipse

    Args:
        x           : x coordinate of n-gon center
        y           : y coordinate of n-gon center
        specs       : array of (a,b) lengths of principal axes of the ellipse(s)
        orientation : inclination of the principal axis of the ellipse in radians
        arc         : (a1,a2) angles in radians of the subtended arc of the n-gon vertices
        npoints     : number of vertices

    Notes:
        | since the resulting paths are closed, the computation yields npoints+1 vertices;
        | thus, arcs that do not correspond to a full circle result in an extra vertex
    '''
    t_vals = np.linspace(*arc,npoints+1)
    s = np.sin(t_vals)
    c = np.cos(t_vals)
    rot  = np.array([[np.cos(orientation), -np.sin(orientation)],
                     [np.sin(orientation),  np.cos(orientation)]])
    gons = []
    for (a,b) in specs:
        data = np.array(list(zip(a*c,b*s)))
        gons.append( np.tensordot(rot, data.T, axes=[1,0]).T+np.array([x,y]))

    return gons
# ----------------------------------------------------------------------------
def ngon_ring(x=0, y=0, specs=[(1,1),(.9,.9)], arc=(0,2*np.pi), orientation=0, npoints=40,
              line_color='k', line_alpha=1, fill_color='k', fill_alpha=0., label=''):
    ''' an ngon outline with finite width
    '''
    l = ngon_data(x,y, specs=specs, arc=arc,orientation=orientation, npoints=npoints)
    d = ( np.hstack([ l[0][:,0],l[1][::-1,0]] ), np.hstack([ l[0][:,1],l[1][::-1,1]] ) )

    o_b = {'Polygons' : dict( color=fill_color, alpha=fill_alpha, line_alpha=0), 'Path': dict(line_color=line_color, line_alpha=line_alpha) }
    o_m = {'Polygons' : dict( color=fill_color, alpha=fill_alpha),               'Path': dict(color=line_color,      alpha=line_alpha) }
    return (hv.Polygons([d], label=label)*hv.Path([l[0], l[1]])).opts( o_b, backend='bokeh' ) #.opts( o_m, backend='matplotlib ')
#    return hv.Polygons([d], label=label).opts( color=fill_color, alpha=fill_alpha, line_alpha=0 )*\
#           hv.Path([l[0], l[1]]).opts( line_color=line_color, line_alpha=line_alpha)

# ----------------------------------------------------------------------------
def ngon(x=0,y=0, spec=(1,1), orientation=0., arc=(0,2*np.pi), npoints=40,\
         line_color='k', line_alpha=1., color='k', alpha=0., label=''):
    ''' an ngon '''
    e = ngon_data(x,y,specs=[spec], arc=arc,orientation=orientation,npoints=npoints)

    return hv.Polygons(e, label=label).opts( line_color=line_color,line_alpha=line_alpha, color=color, alpha=alpha)


import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from functools import reduce

def blockMatrix(blocks):
    """creates a bloack 0-1 matrix.
    param blocks: list of non-negative integers which is the size of the blocks.
    a 0 block size corresponds to a 0 on the main diagonal.
    """
    blocks = np.array(blocks).astype("int64")

    def f(x):
        return 1 if x == 0 else x

    n = np.sum([f(x) for x in blocks])
    n = int(n)
    A = np.zeros((n, n))
    pos = 0
    for i in range(len(blocks)):
        b = blocks[i]
        if b > 0:
            A[pos : pos + b, pos : pos + b] = np.ones((b, b))
        pos += f(b)
    return A

def permutationMatrix(ls):
    """returns a permutation matrix of size len(ls)^2.
    param ls: should be a reordering of range(len(l s)), which defines the
    permutation on the ROWS.
    returns a permutation matrix P.
    np.dot(P,A) should be rearrangement of the rows of A according to P.
    To permute the columns of a matrix A use:
    Q = np.transpose(P),
     then: np.dot(A,Q).
    """
    n = len(ls)
    P = np.zeros((n, n))
    for i in range(n):
        P[i, ls[i]] = 1
    return P

def shuffleCopyMatrix(lins, louts, msize):
    """Returns a matrix P that represents switch and copy operations
    on the rows of a matrix.
    param msize: the size (of the square matrix).
    param lins: row indices to be replaced.
    param louts: row that replace the ones listed in lins.
    lins and louts must be of the same length and contain indiced within
    range(msize).
    These operations are performed on the identity matrix, and the result
    is the return value P.
    """
    # P = np.zeros((msize,msize))
    P = np.identity(msize)
    I = np.identity(msize)
    if not len(lins) == len(louts):
        return P
    for i in range(len(lins)):
        P[lins[i]] = I[louts[i]]
    return P

def scoreMatrix(n):
    """The score function of the matrix. The assumption is that the true
    arrangement maximized the interaction close to the main diagonal.
    The total sum of the interaction is an invariant, preserved by permuations.
    param n: size of ca 2-d n over n array.
    returns the score matrix, which is used to calculate the score of any given
    n^2 matrix.
    """
    s = np.arange(n)
    s = np.exp(-s)
    S = np.zeros((n, n))
    for i in range(n):
        S[i][i:] = s[: n - i]
    return S

def score(A, S):
    """returns the weighted sum (by the score matrix S) of
    the matrix A
    """
    return np.sum(A * S)

def constrainMatrix(ls, A):
    """Returns a matrix of the same dimension as A, but every entry with
    an index (either row or column) not in ls is 0.
    """
    B = np.zeros_like(A)
    # B[np.ix_(ls,ls)] = 1
    B[np.ix_(ls, ls)] = A[np.ix_(ls, ls)]
    # B[ls][:,ls] = A[ls][:,ls]
    return B

def resetIndices(ls, A):
    """essentially returns the constraint of A to the complement indices of ls,
    by reseting all the indices in ls to 0.
    """
    B = A.copy()
    B[ls, :] = 0
    B[:, ls] = 0
    return B

def reindexMatrix(iss, jss, A):
    """iss and jss are lists of indices of equal size, representing
    a permuation: iss[i] is replaced with jss[i]. all other indices which are
    not in the lists left unchanged.
    """
    n = len(A)
    B = np.zeros_like(A)
    tss = [i for i in range(n)]
    for i in range(len(iss)):
        tss[iss[i]] = jss[i]
    for i in range(n):
        for j in range(n):
            B[i, j] = A[tss[i], tss[j]]
    return B

# example
x = np.arange(25).reshape((5, 5))
reindexMatrix([1, 3], [3, 1], x)

def scorePair(iss, jss, refmat, scoremat):
    A = np.zeros_like(refmat)
    l = iss + jss
    n = len(l)
    for i in range(n):
        for j in range(n):
            A[i, j] = refmat[l[i], l[j]]
    return score(A, scoremat)

def scorePair2(iss, jss, refmat):
    """scores the interaction of two segments
    iss and jss. weighted by the ideal diagonal distribution.
    """
    s = 0
    temp = 0
    for i in range(len(iss)):
        for j in range(len(jss)):
            temp = np.exp(-np.abs(j + len(iss) - i))
            # we only care about interaction between the 2 segments and not
            # inside each one of them which wouldn't be affected by
            # rearrangement.
            s += refmat[iss[i], jss[j]] * temp
    return s

def scorePair3(iss, jss, refmat, lreverse=False, rreverse=False):
    """iss, jss must be lists of segments of the index range of refmat,
    our reference matrix.
    reurns the interaction score of iss and jss as if reindexed the matrix so
    that they will be adjuscent to each other.
    """
    s = 0
    temp = 0
    for i in range(len(iss)):
        for j in range(len(jss)):
            x = iss[i]
            y = jss[j]
            if lreverse:
                x = iss[-1 - i]
            if rreverse:
                y = jss[-1 - j]
            # temp = np.exp(-np.abs(i-j))
            # temp = np.exp(-np.abs(x - y))
            temp = np.exp(-np.abs(j + len(iss) - i))
            # we only care about interaction between the 2 segments and not
            # inside each one of them which wouldn't be affected by
            # rearrangement.
            s += refmat[x, y] * temp
    return s

# and the corrsponding indices are:
def articulate(l):
    """l is a list of positive integers.
    returns the implied articulation, meaning a list of lists (or 1d arrays)
    ls, such that ls[0] it the numbers 0 to l[0]-1, ls[1] is a list of the
    numbers ls[1] to ls[2]-1 etc.
    """
    # ls = [np.arange(l[0]).astype('uint64')]
    ls = []
    offsets = np.cumsum([0] + l)
    for i in range(0, len(l)):
        xs = np.arange(l[i]).astype("uint64") + offsets[i]
        ls.append(xs)
    return ls

# example:
articulate([5, 7, 3])

plt.ion()

blocks = [4, 4, 4, 4, 4]
segs = [5, 5, 4, 6]
articulate(blocks)
articulate(segs)

# the block matirx
X = blockMatrix(blocks)
X
plt.imshow(X)

ls = articulate(segs)
ls

scorePair2(ls[0], ls[0], X)
scorePair2(ls[0], ls[1], X)
scorePair2(ls[0], ls[2], X)
scorePair2(ls[0], ls[3], X)

plt.matshow(constrainMatrix(ls[0], X))
plt.matshow(constrainMatrix(ls[1], X))
plt.matshow(constrainMatrix(ls[2], X))
plt.matshow(constrainMatrix(ls[3], X))

# lets shuffle the segments
rs = articulate(segs)

# lets reverse segments 0 and 2
Y = blockMatrix(blocks)
Y

rs = articulate(segs)
rs[2] = np.flip(rs[2])
rs
#

Y = reindexMatrix(ls[2], rs[2], Y)

plt.matshow(X)
plt.matshow(Y)

rs[0] = np.flip(rs[0])
rs

Y = reindexMatrix(ls[0], rs[0], Y)

plt.matshow(X)
plt.matshow(Y)

plt.matshow(constrainMatrix(ls[0], Y))
plt.matshow(constrainMatrix(ls[1], Y))
plt.matshow(constrainMatrix(ls[2], Y))
plt.matshow(constrainMatrix(ls[3], Y))

# now lets change the orfer of the segments
neworder = [1, 3, 0, 2]

newindices = [ls[i] for i in neworder]
newindices
newindices = reduce(lambda x, y: x + list(y), newindices, [])
newindices

Z = reindexMatrix(list(range(len(Y))), newindices, Y)

plt.matshow(X)
plt.matshow(Y)
plt.matshow(Z)

plt.matshow(constrainMatrix(ls[0], Z))
plt.matshow(constrainMatrix(ls[0], X))

plt.matshow(constrainMatrix(ls[1], Z))
plt.matshow(constrainMatrix(ls[2], Z))
plt.matshow(constrainMatrix(ls[3], Z))

newsegments = [len(ls[i]) for i in neworder]
newsegments
zs = articulate(newsegments)
zs

plt.matshow(constrainMatrix(ls[0], Z))
plt.matshow(constrainMatrix(zs[1], Z))
plt.matshow(constrainMatrix(zs[2], Z))
plt.matshow(constrainMatrix(zs[3], Z))

scorePair2(zs[0], zs[0], Z)
scorePair2(zs[0], zs[1], Z)
scorePair2(zs[0], zs[2], Z)
scorePair2(zs[0], zs[3], Z)

scorePair2(ls[1], ls[0], Y)
scorePair2(ls[1], ls[1], Y)
scorePair2(ls[1], ls[2], Y)
scorePair2(ls[1], ls[3], Y)

scorePair3(ls[2], ls[3], Y)
scorePair3(ls[2], ls[3], X)
scorePair3(ls[2], ls[3], Y, lreverse=True)

# def improve(A, xs):
#    temp = np.random.randint(1, len(xs))
#    iss = xs[0]
#    jss = xs[temp]
#    # we're going to see if iss and jss belong together in some configuration
#    sl = scorePair3(iss,jss, A)
#    slrv = scorePair3(iss,jss, A, lreverse=True)
#    sr = scorePair3(jss,iss, A)
#    srrv = scorePair3(jss,iss, A, rreverse=True)
#    t = np.max([sl, slrv, sr, srrv])
#    neworder = [len(x) for x in xs]
#    neworder[1] = len(xs[temp])
#    neworder[temp] = len(xs[1])
#    B = np.zeros_like(A)
#    if t == 0:
#        return xs, A
#    if t == sl:
#        newindices = [xs[i] for i in neworder]
#        newindices = reduce(lambda x,y: x + list(y), newindices, [])
#        B = reindexMatrix(list(range(len(A))), newindices, A)
#    elif t == sr:
#        p = neworder[0]
#        neworder[0] = neworder[1]
#        neworder[1] = p
#        newindices = [xs[i] for i in neworder]
#        newindices = reduce(lambda x,y: x + list(y), newindices, [])
#        B = reindexMatrix(list(range(len(A))), newindices, A)
#    elif t == slrv:
#        # first flip the segment 0
#        rs = xs.copy()
#        rs[0] = np.flip(rs[0])
#        B = reindexMatrix(xs[0], rs[0], A)
#        # then switch segment temp with 1
#        newindices = [xs[i] for i in neworder]
#        newindices = reduce(lambda x,y: x + list(y), newindices, [])
#        B = reindexMatrix(list(range(len(B))), newindices, B)
#    else:
#        # first flip the segment 0
#        rs = xs.copy()
#        rs[0] = np.flip(rs[0])
#        B = reindexMatrix(xs[0], rs[0], A)
#        # then make the switch
#        p = neworder[0]
#        neworder[0] = neworder[1]
#        neworder[1] = p
#        newindices = [xs[i] for i in neworder]
#        newindices = reduce(lambda x,y: x + list(y), newindices, [])
#        B = reindexMatrix(list(range(len(B))), newindices, B)
#    newsegs = [len(xs[i]) for i in neworder if i != 0]
#    newsegs[0] += len(xs[0])
#    newsegs = articulate(newsegs)
#    return newsegs, B

plt.matshow(Z)
zs

ws, W = improve(Z, zs)

# The basic idea is that we are given a matrix and its associated articulation,
# and we need to perform a possible reverese of on segmentm, and relocate
# a segment or two so they will be neighbors. we need to return the new
# articulation structure.

def flip1(s, A, arts):
    """flips (reverses) the s'th segment, as listed by arts.
    returns new matrix.
    param s: the segment to flip.
    param A: the matrix.
    param arts: the articulation of A.
    """
    myarts = arts.copy()
    myarts[s] = np.flip(myarts[s])
    B = reindexMatrix(arts[s], myarts[s], A)
    return B

# example
fooblocks = [4, 6, 2, 7, 11, 6]
foo = blockMatrix(fooblocks)

foosegs = articulate([9, 15, 12])

plt.matshow(foo)
plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[0], foo))

# flip segment 2
bar = flip1(1, foo, foosegs)
plt.matshow(foo)

plt.matshow(bar)

plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[1], bar))

def indexing(arts):
    return reduce(lambda x, y: x + list(y), arts, [])

def swap2(s, r, A, arts):
    """swaps segments s and r, and returns the new
    matrix and the new segmentation.
    """
    myarts = arts.copy()
    myarts[s] = arts[r]
    myarts[r] = arts[s]
    B = reindexMatrix(indexing(arts), indexing(myarts), A)
    newarts = articulate([len(x) for x in myarts])
    return newarts, B

barsegs, bar = swap2(0, 2, bar, foosegs)

def improve(A, xs):
    """param: A: matrix.
    param xs: associated articulation.
    checks if segments 0 and 1 should be attached in some particular order.
    """
    if len(xs) == 1:
        return xs, A
    iss = xs[0]
    jss = xs[1]
    # we're going to see if iss and jss belong together in some configuration
    sl = scorePair3(iss, jss, A)
    slrv = scorePair3(iss, jss, A, lreverse=True)
    sr = scorePair3(jss, iss, A)
    srrv = scorePair3(jss, iss, A, rreverse=True)
    t = np.max([sl, slrv, sr, srrv])
    mysegs = [len(xs[i]) for i in range(1, len(xs))]
    mysegs[0] += len(xs[0])
    mysegs = articulate(mysegs)
    if t == 0:
        return xs, A
    if t == sl:
        # nothin to change
        return mysegs, A
    elif t == sr:
        # swap 0 and 1 segments
        _, B = swap2(1, 0, A, xs)
        return mysegs, B
    elif t == slrv:
        # first flip the segment 0
        B = flip1(0, A, xs)
        return mysegs, B
    else:
        # first flip the segment 0
        B = flip1(0, A, xs)
        # then make the switch
        _, B = swap2(1, 0, B, xs)
        return mysegs, B

# tests

# example
fooblocks = [4, 6, 2, 7, 11, 6]
foo = blockMatrix(fooblocks)

foosegs = articulate([9, 15, 12])

plt.matshow(foo)
plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[0], foo))

# flip segment 2
bar = flip1(1, foo, foosegs)
plt.matshow(foo)
plt.matshow(bar)

plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[1], bar))

barsegs, bar = improve(bar, foosegs)

plt.matshow(foo)

plt.matshow(bar)

barsegs, bar = improve(bar, barsegs)
plt.matshow(bar)

# now flip and replace
bar = flip1(0, foo, foosegs)
barsegs, bar = swap2(0, 2, bar, foosegs)
plt.matshow(foo)
plt.matshow(bar)

barsegs, bar = improve(bar, barsegs)
plt.matshow(bar)

# a bigger experiment

fooblocks = [15, 17, 19, 20, 10, 21, 30, 21, 40, 9, 27, 19]
foo = blockMatrix(fooblocks)

np.sum(fooblocks)

foosegs = [38, 34, 32, 38, 37, 34, 35]
np.sum(foosegs)
np.sum(fooblocks)

foosegs = articulate(foosegs)

plt.matshow(foo)

plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[0], foo))

# now perform some flips and swaps
bar = flip1(1, foo, foosegs)
bar = flip1(5, foo, foosegs)
barsegs, bar = swap2(0, 3, bar, foosegs)
barsegs, bar = swap2(1, 2, bar, barsegs)
barsegs, bar = swap2(5, 2, bar, barsegs)
barsegs, bar = swap2(5, 0, bar, barsegs)
barsegs, bar = swap2(4, 1, bar, barsegs)

plt.matshow(foo)
plt.matshow(bar)

while len(barsegs) > 1:
    x = np.random.randint(1, len(barsegs))
    barsegs, bar = swap2(1, x, bar, barsegs)
    barsegs, bar = improve(bar, barsegs)
plt.matshow(foo)
plt.matshow(bar)
# yeshhhhh!!!!!!!


"""
CSCC11 - Introduction to Machine Learning, Winter 2020, Exam
B. Chan, E. Franco, D. Fleet

===========================================================
 COMPLETE THIS TEXT BOX:

 Student Name: Abhishek Chatterjee
 Student number: 1004820615
 UtorID: chatt114

 I hereby certify that the work contained here is my own

 _Abhishek Chatterjee_
 (sign with your name)
===========================================================
"""

import numpy as np

class PolynomialRegression:
    def __init__(self, K):
        """ This class represents a polynomial regression model which is similar
        to Assignment 1, but we want this to work with multi-dimensional inputs.

        self.parameters contains the model weights.
        NOTE: We have no requirement regarding to the order of parameters.
              e.g. the bias term does not need to be the first element of self.parameters.

        NOTE: Fit the model before calling predict.

        TODO: You will need to implement the methods of this class:
        - transform_matrix: ndarray -> ndarray
        - predict: ndarray -> ndarray
        - fit: ndarray -> None

        Implementation description will be provided under each method.

        For the following:
        - N: Number of samples.
        - D: Dimension of input feature vectors.
        - K: Degree of polynomial model.
        - W: Number of parameters (including bias).

        Args:
        - K (int): The degree of the desired polynomial model. Note: K  is either 1 or 2.
        """
        assert 1 <= K <= 2, f"polynomial order K must be 1 or 2. Got: {K}"
        self.K = K

        self.parameters = None

    def transform_matrix(self, X):
        """  This method transforms matrix of N inputs (feature vectors) into
        the polynomial basis-function matrix (the B matrix in course notes).

        NOTE: We will not be checking the shape of B for you.
              Please make sure the shape is correct (i.e. how many terms should we have?).

        Args:
        - X (ndarray (shape: (N, D))): NxD matrix consisting of N D-dimensional feature vectors

        Output:
        - B (ndarray (shape: (N, W))): NxW matrix consisting of N W-dimensional transformed feature
                                       vectors representing the polynomial basis function matrix.
        """
        (N, D) = X.shape

        # ====================================================
        # TODO: Implement your solution within the box

        # B matrix is Vandermonde matrix with feature vectors

        linear_X = np.ndarray(shape = (N, 1))

        for i in range(N):
            linear_X[i] = np.linalg.norm(X[i], ord=2, axis=0)   # computing 2-norm of matrix

        B = np.vander(linear_X.flatten(), self.K+1, True)

        # ====================================================

        return B

    def predict(self, X):
        """  This method takes as input a matrix of input feature vectos and outputs predicted values
        using the polynomial regression model.

        Args:
        - X (ndarray (shape: (N, D))): NxD matrix consisting of N D-dimensional feature vectors.

        Output:
        - pred (ndarray (shape: (N, 1))): A N-column vector consisting N scalar output data.
        """
        assert len(X.shape) == 2, f"X must be a 2D array. Got: {X.shape}"

        # ====================================================
        # TODO: Implement your solution within the box

        B = self.transform_matrix(X)   # check if this behaves as expected
        pred = np.matmul(B, self.parameters)

        # ====================================================

        assert pred.shape == (X.shape[0], 1), f"dimensionality of predictions is not correct. Expected: {(X.shape[0], 1)}. Got: {pred.shape}"
        return pred

    def fit(self, train_X, train_y):
        """ This method fits the model parameters, given the training inputs and outputs.

        NOTE: You will need to replace self.parameters with the computed optimal parameters.
		Remember that the shape of the self.parameters is (W, 1).

        NOTE: We will not be checking the shape of self.parameters for you.
              Please make sure the shape is correct (i.e. how many parameters should we have?).

        Args:
        - train_X (ndarray (shape: (N, D))): A N-D matrix consisting of N-D dimensional training inputs.
        - train_y (ndarray (shape: (N, 1))): A N-column vector consisting of N scalar training outputs.
        """
        assert len(train_X.shape) == len(train_y.shape) == 2 and train_X.shape[0] == train_y.shape[0], f"input and/or output has incorrect shape (train_X: {train_X.shape}, train_y: {train_y.shape})."

        # ====================================================
        # TODO: Implement your solution within the box

        B = self.transform_matrix(train_X)
        p1 = np.linalg.pinv(B)

        self.parameters = np.matmul(p1, train_y)

        # ====================================================


import numpy as np

def mean_square_error(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate MSE loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    MSE of given predictions
    """
    res = (y_pred - y_true) ** 2
    final = np.mean(res)
    return final

def misclassification_error(y_true: np.ndarray, y_pred: np.ndarray, normalize: bool = True) -> float:
    """
    Calculate misclassification loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values
    normalize: bool, default = True
        Normalize by number of samples or not

    Returns
    -------
    Misclassification of given predictions
    """
    single_loss = np.vectorize(lambda x, y: 1 if x * y < 0 else 0)
    res = single_loss(y_true, y_pred)
    sum_all = np.sum(res)
    if not normalize:
        return sum_all
    return sum_all / (len(y_true) * 1.0)

def accuracy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate accuracy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Accuracy of given predictions
    """
    P = (y_pred == 1).sum()
    N = (y_pred != 1).sum()
    TP = ((y_pred == 1) & (y_true == 1)).sum()
    TN = ((y_pred != 1) & (y_true != 1)).sum()
    return (TP + TN) / (P + N)

def cross_entropy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate the cross entropy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Cross entropy of given predictions
    """
    raise NotImplementedError()


import numpy as np
from scipy.interpolate import CubicSpline
from scipy.fftpack import fft  # built-in fft

# 1.  Using DFT
#     Return c_k series (k = 0, 1, -1, 2, -2, ..., order, -order)
def coeffs_mydft(x, order):
    """Use DFT."""
    coeffs = [my_dft(x, 0)]
    for k in range(1, order+1):
        coeffs.extend([my_dft(x, k), my_dft(x, -k)])
    coeffs = np.array(coeffs) / len(coeffs)
    return coeffs

# 2. Using linear interpolation & power-2 FFT
#    Return c_k series (k = 0, 1, -1, 2, -2, ..., order, -order)
def coeffs_myfft(x, order):
    """Use linear interpolation & power-2 FFT."""
    # Linear interpolation
    x = np.asarray(x)
    N = np.power(2, int(np.rint(np.log2(x.shape[0]))))
    t = np.linspace(0, 1, num=x.shape[0])
    t_new = np.linspace(0, 1, num=N)
    x_new = lin_interp(t_new, t, x)

    # Power-2 FFT
    coeffs = my_fft_pow_two(x_new) / len(x_new)
    coeffs = sort_coeffs(coeffs, N=2*order+1)
    return coeffs

# 3. Using cubic interpolation & power-2 FFT
#    Return c_k series (k = 0, 1, -1, 2, -2, ..., order, -order)
def coeffs_myfft_plus(x, order):
    """Use cubic spline interpolation & power-2 FFT."""
    x = np.asarray(x)
    N = np.power(2, int(np.rint(np.log2(x.shape[0]))))
    t = np.linspace(0, 1, num=x.shape[0])
    t_new = np.linspace(0, 1, num=N)
    # x_new = cubic_spline_prd(t_new, t, x)
    cs = CubicSpline(t, x)
    x_new = cs(t_new)

    # Power-2 FFT
    coeffs = my_fft_pow_two(x_new) / len(x_new)
    coeffs = sort_coeffs(coeffs, N=2*order+1)
    return coeffs

# 4. Using built-in FFT
#    Return c_k series (k = 0, 1, -1, 2, -2, ..., order, -order)
def coeffs_fft(x, order):
    """Use built-in FFT."""
    coeffs = sort_coeffs(fft(x), N=2*order+1) / len(x)
    return coeffs

# Real coordinates convert to complex number
def real2complex(x, y):
    """return: x + y * j"""
    x, y = np.asarray(x), np.asarray(y)
    return x + 1j * y

# DFT implementation
def my_dft(x, k):
    """Return k-th coeff for DFT."""
    x = np.asarray(x)
    N = x.shape[0]
    exp_term = np.exp(-2j * np.pi * k * np.arange(N) / N)
    coeff = np.dot(exp_term, x)
    return coeff

# Power-2 FFT implementation
def my_fft_pow_two(x):
    """Compute power-2 FFT using iteration."""
    x = np.asarray(x)
    N = x.shape[0]
    x_even = x[::2]
    x_odd = x[1::2]

    if N >= 4:
        coeff_even = my_fft_pow_two(x_even)
        coeff_odd = my_fft_pow_two(x_odd)
        coeff_odd = coeff_odd * np.exp(-2j * np.pi * np.arange(int(N/2)) / N)
        coeffs = np.concatenate([coeff_even + coeff_odd, coeff_even - coeff_odd])
    else:
        coeffs = np.dot(np.array([[1, 1], [1, -1]]), x)
    return coeffs

# Linear interpolation
def lin_interp(x, xp, fp):
    """Linear interplation."""
    x, xp, fp = np.asarray(x), np.asarray(xp), np.asarray(fp)
    f_list = []
    for xx in x:
        idx = np.argmax(xp>xx)
        f_list.append(
            (fp[idx-1]*(xp[idx]-xx) + fp[idx]*(xx-xp[idx-1])) / (xp[idx]-xp[idx-1]))
    return np.array(f_list)

# Periodic cubic spline interpolation
def cubic_spline_prd(x, xp, fp):
    """Periodic cubic spline interpolation."""
    # To be done
    pass

# Sort the coefficients in order 0, 1, -1, 2, -2, ..., order, -order
def sort_coeffs(coeffs, N=None):
    """Sort coefficients in order 0, 1, -1, 2, -2, ..., order, -order."""
    coeffs = np.asarray(coeffs)
    if N is None:
        N = coeffs.shape[0]
    sorted_list = [coeffs[0]]
    for i in range(1, int((N+1)/2)):
        sorted_list.extend([coeffs[i], coeffs[-i]])
    if N % 2 == 0:
        sorted_list.append(coeffs[int(N/2)])
    return np.array(sorted_list)


import numpy as np
import pandas as pd
from scipy import ndimage
from scipy.cluster import hierarchy
from scipy.spatial import distance_matrix
from matplotlib import pyplot as plt
from sklearn import manifold, datasets
from sklearn.cluster import AgglomerativeClustering
from sklearn.datasets.samples_generator import make_blobs

X1, y1 = make_blobs(n_samples=50, centers=[[4,4], [-2, -1], [1, 1], [10,4]], cluster_std=0.9)

plt.scatter(X1[:, 0], X1[:, 1], marker='o')

agglom = AgglomerativeClustering(n_clusters = 4, linkage = 'average')

agglom.fit(X1,y1)

# Create a figure of size 6 inches by 4 inches.
plt.figure(figsize=(6, 4))

# These two lines of code are used to scale the data points down,
# Or else the data points will be scattered very far apart.

# Create a minimum and maximum range of X1.
x_min, x_max = np.min(X1, axis=0), np.max(X1, axis=0)

# Get the average distance for X1.
X1 = (X1 - x_min) / (x_max - x_min)

# This loop displays all of the datapoints.
for i in range(X1.shape[0]):
    # Replace the data points with their respective cluster value
    # (ex. 0) and is color coded with a colormap (plt.cm.spectral)
    plt.text(X1[i, 0], X1[i, 1], str(y1[i]),
             color=plt.cm.nipy_spectral(agglom.labels_[i] / 10.),
             fontdict={'weight': 'bold', 'size': 9})

# Remove the x ticks, y ticks, x and y axis
plt.xticks([])
plt.yticks([])
# plt.axis('off')

# Display the plot of the original data before clustering
plt.scatter(X1[:, 0], X1[:, 1], marker='.')
# Display the plot
plt.show()

dist_matrix = distance_matrix(X1,X1)
print(dist_matrix)

Z = hierarchy.linkage(dist_matrix, 'complete')

dendro = hierarchy.dendrogram(Z)

#wget -O cars_clus.csv https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-ML0101EN-SkillsNetwork/labs/Module%204/data/cars_clus.csv

filename = 'cars_clus.csv'

#Read csv
pdf = pd.read_csv(filename)
print ("Shape of dataset: ", pdf.shape)

pdf.head(5)

print ("Shape of dataset before cleaning: ", pdf.size)
pdf[[ 'sales', 'resale', 'type', 'price', 'engine_s',
       'horsepow', 'wheelbas', 'width', 'length', 'curb_wgt', 'fuel_cap',
       'mpg', 'lnsales']] = pdf[['sales', 'resale', 'type', 'price', 'engine_s',
       'horsepow', 'wheelbas', 'width', 'length', 'curb_wgt', 'fuel_cap',
       'mpg', 'lnsales']].apply(pd.to_numeric, errors='coerce')
pdf = pdf.dropna()
pdf = pdf.reset_index(drop=True)
print ("Shape of dataset after cleaning: ", pdf.size)
pdf.head(5)

featureset = pdf[['engine_s',  'horsepow', 'wheelbas', 'width', 'length', 'curb_wgt', 'fuel_cap', 'mpg']]

from sklearn.preprocessing import MinMaxScaler
x = featureset.values #returns a numpy array
min_max_scaler = MinMaxScaler()
feature_mtx = min_max_scaler.fit_transform(x)
feature_mtx [0:5]

import scipy
leng = feature_mtx.shape[0]
D = scipy.zeros([leng,leng])
for i in range(leng):
    for j in range(leng):
        D[i,j] = scipy.spatial.distance.euclidean(feature_mtx[i], feature_mtx[j])

import pylab
import scipy.cluster.hierarchy
Z = hierarchy.linkage(D, 'complete')

from scipy.cluster.hierarchy import fcluster
max_d = 3
clusters = fcluster(Z, max_d, criterion='distance')
clusters

from scipy.cluster.hierarchy import fcluster
k = 5
clusters = fcluster(Z, k, criterion='maxclust')
clusters

fig = pylab.figure(figsize=(18, 50))

def llf(id):
    return '[%s %s %s]' % (pdf['manufact'][id], pdf['model'][id], int(float(pdf['type'][id])))

dendro = hierarchy.dendrogram(Z, leaf_label_func=llf, leaf_rotation=0, leaf_font_size=12, orientation='right')

dist_matrix = distance_matrix(feature_mtx,feature_mtx)
print(dist_matrix)

agglom = AgglomerativeClustering(n_clusters = 6, linkage = 'complete')
agglom.fit(feature_mtx)
agglom.labels_

pdf['cluster_'] = agglom.labels_
pdf.head()

import matplotlib.cm as cm
n_clusters = max(agglom.labels_)+1
colors = cm.rainbow(np.linspace(0, 1, n_clusters))
cluster_labels = list(range(0, n_clusters))

# Create a figure of size 6 inches by 4 inches.
plt.figure(figsize=(16,14))

for color, label in zip(colors, cluster_labels):
    subset = pdf[pdf.cluster_ == label]
    for i in subset.index:
            plt.text(subset.horsepow[i], subset.mpg[i],str(subset['model'][i]), rotation=25)
    plt.scatter(subset.horsepow, subset.mpg, s= subset.price*10, c=color, label='cluster'+str(label),alpha=0.5)
#    plt.scatter(subset.horsepow, subset.mpg)
plt.legend()
plt.title('Clusters')
plt.xlabel('horsepow')
plt.ylabel('mpg')

pdf.groupby(['cluster_','type'])['cluster_'].count()

agg_cars = pdf.groupby(['cluster_','type'])['horsepow','engine_s','mpg','price'].mean()
agg_cars

plt.figure(figsize=(16,10))
for color, label in zip(colors, cluster_labels):
    subset = agg_cars.loc[(label,),]
    for i in subset.index:
        plt.text(subset.loc[i][0]+5, subset.loc[i][2], 'type='+str(int(i)) + ', price='+str(int(subset.loc[i][3]))+'k')
    plt.scatter(subset.horsepow, subset.mpg, s=subset.price*20, c=color, label='cluster'+str(label))
plt.legend()
plt.title('Clusters')
plt.xlabel('horsepow')
plt.ylabel('mpg')


# Conversion from Mathlab to Python:
# http://www2.meteo.uni-bonn.de/staff/venema/themes/surrogates/pmodel/pmodel.m

# *** function varargout = pmodel(noValues, p, slope) ***
# This function generates a multifractal time series using the p-model.
# Optionally you can also filter the result from the p-model in Fourier
# space to give it another fractal slope, e.g. to make it continuous and
# nonstationary. This is also called fractional integration. The p-model
# itself can only produce stationary time series, i.e time series where
# the variance is finite if you would extrapolate its power spectrum to
# infinite large scales.
#
# The parameter of the p-model is p. With p values close to 1 or 0 the time
# series is very peaked. With values close to 0.5 the p-model is much
# calmer; p=0.5 results in a constant unity vector.
# The parameter for the fractal integration is the slope of the power
# spectrum. Davis et al. calls slopes flatter than -1 stationary, and
# slopes between -1 and -3 nonstationary, with stationary increments.
# These nonstationary cases are at least continuous, but not
# differentiable. Slopes steeper than -3 are nonstionary and
# differentiable.
#
# Possible calls:
# [pModelTimeSeries] = pmodel
# [pModelTimeSeries] = pmodel(noValues)
# [pModelTimeSeries] = pmodel(noValues, p)
# [fractionalIntegratedTimeSeries] = pmodel(noValues, p, slope)
# [fractionalIntegratedTimeSeries, pModelTimeSeries] = pmodel(noValues, p, slope)
# If no number of values (noValues) is specified or it is empty, the
# default value of 256 is used.
# If no p is specified or it is empty, the default value of 0.375 is used.
# If no slope is specified, you will get a time series without fractional
# integration.
# If a slope is specified you will get the fractionally integrated time
# series, or if you use two output variables you will get both the p-model
# time series as well as its fractionally integrated version.
#
# This Matlab function is based on the article: Davis, A., A. Marshak, R.
# Cahalan, and W. Wiscombe, The landsat scale break in stratocumulus as a
# three-dimensional radiative transfer effect: implications for cloud
# remote sensing. Journal of the Atmospheric Sciences, Vol. 54, no. 2, 1997.
# First version by Victor Venema, Victor.Venema@uni-bonn.de, 25 January 2006.

# NumPy is the fundamental package for scientific computing with Python
import numpy as np
# This module provides access to the math functions defined by the C standard
import math

# function y2 = next_step_1d(y, p)
def next_step_1d(y, p):
    length = len(y)                         # len = numel(y);
    y2 = np.zeros(length * 2)               # y2 = zeros(1,len*2);
    sign = np.random.uniform(0, 1, length) - 0.5  # sign=rand(1, len)-0.5;
    sign = sign / abs(sign)                 # sign = sign./abs(sign);
    y2[::2] = y + sign * (1 - 2 * p) * y    # y2(1:2:2*len-1)=y+sign.*(1-2*p).*y;
    y2[1::2] = y - sign * (1 - 2 * p) * y   # y2(2:2:2*len)  =y-sign.*(1-2*p).*y;
    return y2

#function a=fractal_spectrum_1d(noValues, slope)
def fractal_spectrum_1d(noValues, slope):
# If you want to make a large number of time series, please rewrite this
# part to get rid of the for-loop. :-)
    ori_vector_size = noValues              # ori_vector_size = noValues;
    ori_half_size = int(ori_vector_size / 2)  # ori_half_size=ori_vector_size/2;
    # The magnitudes of the Fourier coefficients
    a = np.zeros(ori_vector_size)           # a = zeros(ori_vector_size,1);
    for t2 in range(1, (ori_half_size+1)+1, 1):   # for t2 = 1:ori_half_size+1
        index = t2 - 1                      # index = t2-1;
        t4 = 2 + ori_vector_size - t2       # t4 = 2 + ori_vector_size - t2;
        if t4 > ori_vector_size :           # if ( t4 > ori_vector_size )
            t4 = t2                         # t4 = t2;
        with np.errstate(divide='ignore') :
            coeff = np.array([index])**slope  # coeff = index.^slope;
        a[t2-1] = coeff                     # a(t2) = coeff;
        a[t4-1] = coeff                     # a(t4) = coeff;
    a[0] = 0    # The DC-component of the Fourier spectrum should be zero
    return a

def pmodel(**kwargs) :
    noValues = kwargs.get('noValues')
    p = kwargs.get('p')
    slope = kwargs.get('slope')

    # Check input
    # if ( nargin < 1 | isempty(noValues) )
    if len(kwargs) < 1 or kwargs.get('noValues') == 'None' :
        noValues = 256                  # noValues = 256;
    # if ( nargin < 2 | isempty(p) )
    if len(kwargs) < 2 or kwargs.get('p') == 'None' :
        p = 0.375                       # p = 0.375;
    if len(kwargs) < 3 :                # if ( nargin < 3 )
        slope = []                      # slope = [];

    # Calculate length of time series
    # noOrders=ceil(log2(noValues));
    noOrders = int(math.ceil(math.log(noValues, 2)))

    # noValuesGenerated = 2.^(noOrders); <== not used

    # y is the time series generated with the p-model.
    y = [1]                             # y = 1;
    for n in range(1, noOrders + 1, 1): # for n=1:noOrders
        y = next_step_1d(y, p)          # y = next_step_1d(y, p);

    # If a slope if specified also a fractionally integrated time series(x)
    # is calculated from y.
    if not slope == [] :                # if ( ~isempty(slope) )
        # Calculate the magnitudes of the coefficients of the Fourier spectrum.
        # The Fourier slope is half of the slope of the power spectrum.
        # fourierCoeff = fractal_spectrum_1d(noValues, slope/2)';
        fourierCoeff = fractal_spectrum_1d(noValues, slope / 2.0)
        meanVal = np.mean(y)            # meanVal = mean(y);
        stdy = np.std(y)                # stdy = std(y);
        # Calculate the Fourier coefficients of the original p-model time series
        x = np.fft.ifft(y - meanVal)    # x = ifft(y-meanVal);
        # Calculate the phases, as these are kept intact, should not be changed
        # by the Fourier filter
        phase = np.angle(x)             # phase = angle(x);
        # Calculate the complex Fourier coefficients with the specified
        # spectral slope, and the phases of the p-model
        x = fourierCoeff * np.exp(1j * phase)  #x = fourierCoeff .* exp(i*phase);
        # Generate the fractionally integrated time series.
        x = np.real(np.fft.fft(x))      # x = real(fft(x));
        x = x * stdy / np.std(x)        # x = x * stdy / std(x);
        x = x + meanVal                 # x = x + meanVal;
    else:
        x = y                           # x = y;

# not implented:
# Reduce the sizes of the time series and put them in the right output variable.
# if (nargout == 1 )
#    y=y(1:noValues);
#    varargout{1} = x;
# else
#    y=y(1:noValues);
#    x=x(1:noValues);
#    varargout{1} = x;
#    varargout{2} = y;
# end

    x = x[0:noValues+1]
    return x

# REFERENCES
# https://stackoverflow.com/questions/25087769/runtimewarning-divide-by-zero-error-how-to-avoid-python-numpy/25088239
# https://stackoverflow.com/questions/20161899/scipy-curve-fit-error-divide-by-zero-encountered
# https://docs.scipy.org
# https://pastebin.com/CxxVrwZF accessed 2019-05-31
# http://www2.meteo.uni-bonn.de/staff/venema/themes/surrogates/pmodel/pmodel.m
# https://www.python.org/
# https://www.gnu.org/software/octave/


import numpy as np
from scipy import misc
import pandas as pd
import os

# 1) Examen

## 2) Crear un vector de ceros de tamaño 10

vector_zeros_2 = np.zeros(10)
print('2) --> ', vector_zeros_2)

## 3) Crear un vector de ceros de tamaño 10 y el de la posicion 5 sea igual a 1

vector_zeros_3 = np.zeros(10)
vector_zeros_3[5] = 1

print('3) --> ', vector_zeros_3)

## 4) Cambiar el orden de un vector de 50 elementos, el de la posicion 1 es el de la 50 etc.

vector_zeros_4 = np.arange(50)
vector_zeros_4 = vector_zeros_4[::-1]

print('4) --> ', vector_zeros_4)

## 5) Crear una matriz de 3 x 3 con valores del cero al 8

matriz = np.arange(9)
matriz = matriz.reshape((3,3))

print('5) --> ', matriz)

## 6) Encontrar los indices que no sean cero en un arreglo

arreglo_indices = [1,2,0,0,4,0]
arreglo_indices = np.array(arreglo_indices)

resultado = np.where(arreglo_indices != 0)[0]

print('6) --> ', resultado)

## 7) Crear una matriz de identidad 3 x 3

matriz_identidad = np.eye(3)
print('7) --> ', matriz_identidad)

## 8) Crear una matriz 3 x 3 x 3 con valores randomicos

matriz_randomica = np.random.randint(27, size=27).reshape(3,3,3)
print('8) --> ', matriz_randomica)

## 9) Crear una matriz 10 x 10 y encontrar el mayor y el menor

matriz_diez = np.arange(100).reshape(10,10)

menor_valor = matriz_diez.min()
mayor_valor = matriz_diez.max()

print('9) menor --> ', menor_valor)
print('9) mayor --> ', mayor_valor)

## 10) Sacar los colores RGB unicos en una imagen (cuales rgb existen ej: 0, 0, 0 - 255,255,255 -> 2 colores)

imagen = misc.face()

resultado = len(np.unique(imagen, axis=0))
print('10) --> ', resultado)

## 11) ¿Como crear una serie de una lista, diccionario o arreglo?

mylist = list('abcdefghijklmnsopqrstuvwxyz')
myarr = np.arange(26)
mydict = dict(zip(mylist, myarr))

serie = pd.Series(mylist)
serie_diccionario = pd.Series(mydict)
serie_arreglo = pd.Series(myarr)

print('11) --> Serie de lista: ', serie, '\n')
print('11) --> Serie de diccionario: ', serie_diccionario, '\n')
print('11) --> Serie de arreglo ', serie_arreglo, '\n')

## 12) ¿Como convertir el indice de una serie en una columna de un DataFrame?

mylist = list('abcedfghijklmnopqrstuvwxyz')
myarr = np.arange(26)
mydict = dict(zip(mylist, myarr))
ser = pd.Series(mydict)

df = pd.DataFrame(ser).reset_index()

# Transformar la serie en dataframe y hacer una columna indice

df1 = pd.DataFrame(ser, index=['a'])

## 13) ¿Como combinar varias series para hacer un DataFrame?

ser1 = pd.Series(list('abcedfghijklmnopqrstuvwxyz'))
ser2 = pd.Series(np.arange(26))

df_combinado = pd.concat([ser1, ser2], axis = 1)
df_combinado = pd.DataFrame(df_combinado)

print('13) --> ', df_combinado)

## 14) ¿Como obtener los items que esten en una serie A y no en una serie B?

ser1 = pd.Series([1, 2, 3, 4, 5])
ser2 = pd.Series([4, 5, 6, 7, 8])

items_diferencia = np.setdiff1d(ser1, ser2)
print('14) --> ', items_diferencia)

## 15) ¿Como obtener los items que no son comunes en una serie A y serie B?

ser1 = pd.Series([1, 2, 3, 4, 5])
ser2 = pd.Series([4, 5, 6, 7, 8])

items_conjuncion = set(ser1) ^ set(ser2)
items_conjuncion = list(items_conjuncion)
items_conjuncion = pd.Series(items_conjuncion)

print('15) --> ', items_conjuncion, '\n')

## 16) ¿Como obtener el numero de veces que se repite un valor en una serie?

ser = pd.Series(np.take(list('abcdefgh'), np.random.randint(8, size=30)))

repeticiones, contador = np.unique(ser, return_counts=True)
repeticiones = dict(zip(repeticiones, contador))

print(repeticiones)
print(contador)

print('16) --> ', repeticiones, '\n')

## 17) ¿Como mantener los 2 valores mas repetidos de una serie, y a los demas valores cambiarles por 0 ?

np.random.RandomState(100)
ser = pd.Series(np.random.randint(1, 5, [12]))

valores_repetidos, contador = np.unique(ser, return_counts=True)

print('serie --> ', ser)
print('contador --> ', ser)
indice = np.argsort(-contador)
print('indice --> ', indice)
valores_repetidos = valores_repetidos[indice]

valores_repetidos[2:] = 0

print('17) --> Valores repetidos', valores_repetidos)

## 18) ¿Como transformar una serie de un arreglo de numpy a un DataFrame con un `shape` definido?

ser = pd.Series(np.random.randint(1, 10, 35))

df_shape = pd.DataFrame(ser.values.reshape(7,5))

print('18) --> ', df_shape)

## 19) ¿Obtener los valores de una serie conociendo la posicion por indice?

ser = pd.Series(list('abcdefghijklmnopqrstuvwxyz'))
pos = [0, 4, 8, 14, 20]
# a e i o u

resultado = ser[pos]

print('19) --> ', resultado)

## 20) ¿Como anadir series vertical u horizontalmente a un DataFrame?

ser1 = pd.Series(range(5))
ser2 = pd.Series(list('abcde'))

#Verical
df1 = pd.concat([pd.DataFrame(),ser2], ignore_index = True)

#Horizontal
df2 = pd.DataFrame().append(ser1, ignore_index=True)

print('20) Vertical --> ', df1)
print('21) Horizontal --> ', df2)

## 21)¿Obtener la media de una serie agrupada por otra serie?

#`groupby` tambien esta disponible en series.

frutas = pd.Series(np.random.choice(['manzana', 'banana', 'zanahoria'], 10))
pesos = pd.Series(np.linspace(1, 10, 10))
print(pesos.tolist())
print(frutas.tolist())
#> [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
#> ['banana', 'carrot', 'apple', 'carrot', 'carrot', 'apple', 'banana', 'carrot', 'apple', 'carrot']

# Los valores van a cambiar por ser random
# apple     6.0
# banana    4.0
# carrot    5.8
# dtype: float64

media_agrupada = pd.concat([frutas, pesos], axis = 1)
media_agrupada = media_agrupada.groupby(media_agrupada[0], as_index=False)[1].mean()

print('21) --> \n', media_agrupada)

## 22)¿Como importar solo columnas especificas de un archivo csv?

#https://raw.githubusercontent.com/selva86/datasets/master/BostonHousing.csv.

path = "./archivo.csv"

data_csv = pd.read_csv(
    path,
    nrows = 10)

columnas = ['crim', 'zn', 'indus']

data_tres_columnas = pd.read_csv(path, nrows=10, usecols=columnas)

print('22) --> ', data_tres_columnas)