import numpy as np
import sklearn.datasets as skdata
import sklearn.metrics as skmetrics
import sklearn.preprocessing as skpreprocess
from sklearn.linear_model import LinearRegression
from matplotlib import pyplot as plt

'''
Name: Kanagaraj, Kanimozhi

Summary:

TODO:
(1) In polynomial Feature exapnsion :
    a. we add an extra dimension to seprate the data at higher dimensions(mapping from linear to non-linear space)
    b. Given dataset{X^n,y^n} , where n = 1,...,N
    c. choose the order of function 1st or 2nd or 3rd polynomial called phi
        polynomial Feature Exapansion algorithn:
            phi_p(X) = (x_0,x_1,x_2,..,x_d,x_1^p,..,x_d^p)
            phi_p(X) = w_0.x_0 + w_1.x_1+...+w_d.x_d + w_d+x_1^p+..+w_D.x_d^p
    d. Map the features from X to Z using function phi
    e. optimize weights by minimizing the loss function to find the best linear hypothesis in non-linear space
    f. for hypothesis we take transpose of weight and phi(X) (in regrssion)

(2) We use this polynomial algortihm:
    a. As a framework for non-linear mapping and to hand-craft features for real world problems
    b. As it is not possible to seprate the data in 2-dimensions
       we add an extra dimension to seprate the data in non-linear mapping

(3) Negative learning the algorithm is prone to ar:
    Overfitting: (when model fits the data too well)
    It happens when model learns to fit both data and noise present, where it has negative impact on new data
    Resulting in incorrect predictions for new data

TODO: Report your scores here. For example,

Results using scikit-learn LinearRegression model with linear features
Training set mean squared error: 23.2560
Training set r-squared scores: 0.7323
Validation set mean squared error: 17.6111
Validation set r-squared scores: 0.7488
Testing set mean squared error: 17.1465
Testing set r-squared scores: 0.7805
Results of LinearRegression model using scikit-learn order-2 polynomial expansion features
Features after polynomial transform order-2: 105
Training set mean squared error: 6.6954
Training set r-squared scores: 0.9229
Validation set mean squared error: 9.5225
Validation set r-squared scores: 0.8642
Testing set mean squared error: 28.2841
Testing set r-squared scores: 0.6378
Results of LinearRegression model using scikit-learn order-3 polynomial expansion features
Features after polynomial transform order-3: 560
Training set mean squared error: 0.0000
Training set r-squared scores: 1.0000
Validation set mean squared error: 131226.9258
Validation set r-squared scores: -1870.9922
Testing set mean squared error: 119705.0919
Testing set r-squared scores: -1531.7112
Results for LinearRegression model using our implementation of order-2 polynomial expansion features
Features after polynomial transform order-2: 105
Training set mean squared error: 6.6954
Training set r-squared scores: 0.9229
Validation set mean squared error: 9.5225
Validation set r-squared scores: 0.8642
Testing set mean squared error: 28.2841
Testing set r-squared scores: 0.6378
Results for LinearRegression model using our implementation of order-3 polynomial expansion features
Features after polynomial transform order-3: 560
Training set mean squared error: 0.0000
Training set r-squared scores: 1.0000
Validation set mean squared error: 131094.4582
Validation set r-squared scores: -1869.1026
Testing set mean squared error: 119633.2842
Testing set r-squared scores: -1530.7918
'''

'''
Implementation of our polynomial expansion for nonlinear mapping
'''
class PolynomialFeatureExpansion(object):

    def __init__(self, degree):
        '''
        Args:
            degree : int
                order or degree of polynomial for expansion
        '''
        # Degree or order of polynomial we will expand to
        self.__degree = degree

        # List of boolean lists (True, False) to represent which polynomials we will create
        # Examples of polynomials:
        # [
        #   [x1^2, x1x2, x2x1, x2^2]  2nd order
        #   [...]
        # ]
        # Corresponding polynomials terms to create:
        # [
        #   [True, True, False, True]  2nd order
        #   [...]
        # ]
        self.__polynomial_terms = []

    def transform(self, X):
        '''
        Computes up to p-order (degree) polynomial features and augments them to the data

        Args:
            X : numpy
                N x d feature vector

        Returns:
            polynomial expanded features in Z space
        '''

        # Initialize the bias
        # TODO: What is the shape of bias and why do we select this shape?
        '''
            shape of bias is (N, 1) , we select this as we need to augmnets up to p-order polynomial
            (ie),as X is a (N,d), we need N number if biases so we create (N,1)
        '''
        bias = np.ones([X.shape[0], 1])
        # Initialize polynomial expansion features Z
        # TODO: Suppose x = [x1, x2], what terms are in Z?
        '''
            Z will have values : [x_0,x_1,x_2,x_3,..x_d,..x_1^p,..x_d^p]
            (Z is agumented to X)
            Z has the bias, terms of X ,polynomial terms
        '''
        Z = [bias, X]

        # If degree is less than 2, then return the original features
        if self.__degree < 2:
            Z = np.concatenate(Z, axis=1)
            return Z

        # Split X into it's d dimensions separately
        linear_features = np.split(X, indices_or_sections=X.shape[1], axis=1)

        if self.__degree == 2:
            # Keep a list of new polynomial features that we've accumulated
            new_polynomial_features = []

            # Keep track of the polynomial terms that we will keep
            polynomial_terms = []

            # For every linear feature
            for l1 in range(len(linear_features)):

                # Multiply it by every linear feature
                for l2 in range(len(linear_features)):

                    # TODO: Suppose x = [x_1, x_2]
                    # write the polynomial terms after each iteration
                    # for 2nd order polynomial
                    '''
                    First Iteration
                        a. x_1 * x_1 = x_1^2
                        b. x_1 * x_2 = x_1 x_2
                    Second Iteration
                        a. x_2 * x_1 = x_2 x_1
                        b. x_2 * x_2 = x_2^2

                    '''
                    polynomial_feature = linear_features[l1] * linear_features[l2]

                    # Check if we have already found the polynomial terms to keep
                    if len(self.__polynomial_terms) < self.__degree - 1:

                        # If we have not, then iterate through the expansion
                        keep_polynomial_term  = True

                        # Check if we already have the feature created
                        for feature in new_polynomial_features:
                            if np.sum(polynomial_feature - feature) == 0.0:
                                keep_polynomial_term = False
                                break

                        # Keep track of whether we keep or discard (True/False) the term
                        polynomial_terms.append(keep_polynomial_term)

                        if keep_polynomial_term:
                            # And append the result to the new set of polynomial features
                            new_polynomial_features.append(polynomial_feature)
                    else:
                        # Check if the current polynomial term was kept
                        # l1 * len(linear_features) + l2 indexes into the term we are creating
                        keep_polynomial_term = self.__polynomial_terms[0][l1 * len(linear_features) + l2]

                        if keep_polynomial_term:
                            # And append the result to the new set of polynomial features
                            new_polynomial_features.append(polynomial_feature)

            # If we've never processed the polynomial terms before, save the list of terms to keep
            if len(self.__polynomial_terms) < self.__degree - 1:
                self.__polynomial_terms.append(polynomial_terms)

            # Add the new polynomial features to Z
            Z.append(np.concatenate(new_polynomial_features, axis=1))

        if self.__degree > 2:
            # Start off with X as both the set of linear and current polynomial features
            linear_features = np.split(X, indices_or_sections=X.shape[1], axis=1)
            current_polynomial_features = linear_features

            # Since we will be taking the difference of the sum of features at every
            # iteration of the inner loop, let's compute their sums first to save compute
            sum_Z_features = [
                np.sum(f) for f in linear_features
            ]

            # For every degree expansion
            for d in range(0, self.__degree - 1):
                # Initialize a list to hold the new polynomial features
                new_polynomial_features = []

                # Keep track of the polynomial terms that we will keep
                polynomial_terms = []

                # Since expanding a polynomial (x1 + x2)^2 to a higher order (x1 + x2)^3 is just
                # multiplying by linear terms e.g. (x1 + x2)^3 = (x1 + x2)^2 (x1 + x2)
                # we treat the outer loop as the current polynomial term (x1 + x2)^2
                # that we are processing

                # For every polynomial feature

                for p in range(len(current_polynomial_features)):
                    # Multiply it by every linear feature
                    for l in range(len(linear_features)):
                        # TODO: Suppose x = [x_1, x_2]
                        # write the polynomial terms after each iteration
                        # for 3rd order polynomial
                        '''
                        First Iteration:
                            1. x_1 * x_1 = x_1^2
                            2. x_1 * x_2 = x1 x_2
                            3. (x_1 * x_1)* x_1 = x_1^3
                            4. (x_1 * x_1) * x_2 = x_1^2 x_2
                            5. (x_1 * x_2) * x_1 = x_1^2 x_2
                            6. (x_1 * x_2) * x_2 = x_2^2 x_1
                        Second Iteration:
                            1. x_2 * x_1 = x_2 x_1
                            2. x_2 * x_2 =  x_2^2
                            3. (x_2 * x_1) * x_1 = x_1^2 x_2
                            4. (x_2 * x_1)* x_2 = x_2^2 x_1
                            5. (x_2 * x_2) * x_1 = x_2^2 x_1
                            6. (x_2 * x_2)* x_2 = x_2^3
                        '''

                        polynomial_feature = current_polynomial_features[p] * linear_features[l]

                        # Check if we have already found the polynomial terms to keep
                        if len(self.__polynomial_terms) < self.__degree - 1:

                            # If we have not, then iterate through the expansion
                            keep_polynomial_term  = True

                            # Check if we already have the feature created
                            # To save some compute sum this once before going into loop
                            sum_polynomial_feature = np.sum(polynomial_feature)

                            for sum_Z_feature in sum_Z_features:
                                # We check if the absolute difference of the sums is less than a small epsilon
                                if np.abs(sum_polynomial_feature - sum_Z_feature) < 1e-9:
                                    keep_polynomial_term = False
                                    break

                            # Keep track of whether we keep or discard (True/False) the term
                            polynomial_terms.append(keep_polynomial_term)

                            if keep_polynomial_term:
                                # And append the result to the new set of polynomial features
                                new_polynomial_features.append(polynomial_feature)
                                sum_Z_features.append(sum_polynomial_feature)

                        else:
                            # Check if the current polynomial term was kept
                            # p * len(linear_features) + l indexes into the term we are creating
                            # TODO: What is d referring to?
                            '''
                                d refers to range of one less than order of degree
                                eg : if range of degree is 0,1,2 then d is 0,1
                            '''
                            # TODO: For third degree expansion of x = [x1, x2],
                            # What terms are we indexing to if we just use p * len(linear_features) instead?
                            '''
                                we will be indexing only current polynomial terms(2nd order terms)
                                ie., l the third term wont be indexed, won't index the created terms
                            '''
                            keep_polynomial_term = self.__polynomial_terms[d][p * len(linear_features) + l]

                            if keep_polynomial_term:
                                # And append the result to the new set of polynomial features
                                new_polynomial_features.append(polynomial_feature)

                # If we've never processed the polynomial terms before, save the list of terms to keep
                if len(self.__polynomial_terms) < self.__degree - 1:
                    self.__polynomial_terms.append(polynomial_terms)

                # Add the new polynomial features to Z
                # TODO: Why do we concatenate along the 1st axis?
                '''
                    new polynomial feature is a list of polynomial features
                    We concatenate along axis 1 to get (N,D) matrices
                    Eg :  degree of 2 we get (N,3), dgree of 3 we get(N,7)

                '''
                Z.append(np.concatenate(new_polynomial_features, axis=1))

                # TODO: For 3rd order polynomial expansion, what does Z contain after
                # each iteration of the outer for loop (d)
                '''
                    Z will contain bias , X(linear features) and polynomial features
                    2nd polynomial feature in first iteration and 3rd order polynomial in next iteration
                '''
                # Set the new polynomial features to curren polynomial features and repeat
                current_polynomial_features = new_polynomial_features

        # Concatenate every term into the feature vector
        Z = np.concatenate(Z, axis=1)

        return Z

if __name__ == '__main__':

    boston_housing_data = skdata.load_boston()
    x = boston_housing_data.data
    y = boston_housing_data.target

    # 80 percent train, 10 percent validation, 10 percent test split
    train_idx = []
    val_idx = []
    test_idx = []
    for idx in range(x.shape[0]):
        if idx and idx % 10 == 9:
            val_idx.append(idx)
        elif idx and idx % 10 == 0:
            test_idx.append(idx)
        else:
            train_idx.append(idx)

    x_train, x_val, x_test = x[train_idx, :], x[val_idx, :], x[test_idx, :]
    y_train, y_val, y_test = y[train_idx], y[val_idx], y[test_idx]

    '''
    Trains and tests linear regression from scikit-learn
    '''
    # Variables to hold training, validation and testing scores for linear up to p-order polynomial expansion
    scores_mse_train = []
    scores_mse_val = []
    scores_mse_test = []
    scores_r2_train = []
    scores_r2_val = []
    scores_r2_test = []

    # Initialize scikit-learn linear regression model
    model = LinearRegression()

    # Trains scikit-learn linear regression model
    model.fit(x_train, y_train)

    print('Results using scikit-learn LinearRegression model with linear features')

    # Test model on training set
    predictions_train = model.predict(x_train)

    score_mse_train = skmetrics.mean_squared_error(predictions_train, y_train)
    print('Training set mean squared error: {:.4f}'.format(score_mse_train))

    score_r2_train = model.score(x_train, y_train)
    print('Training set r-squared scores: {:.4f}'.format(score_r2_train))

    # TODO: Save MSE and R-square scores on training set
    scores_mse_train.append(score_mse_train)
    scores_r2_train.append(score_r2_train)

    # Test model on validation set
    predictions_val = model.predict(x_val)

    score_mse_val = skmetrics.mean_squared_error(predictions_val, y_val)
    print('Validation set mean squared error: {:.4f}'.format(score_mse_val))

    score_r2_val = model.score(x_val, y_val)
    print('Validation set r-squared scores: {:.4f}'.format(score_r2_val))

    # TODO: Save MSE and R-square scores on validation set
    scores_mse_val.append(score_mse_val)
    scores_r2_val.append(score_r2_val)

    # Test model on testing set
    predictions_test = model.predict(x_test)

    score_mse_test = skmetrics.mean_squared_error(predictions_test, y_test)
    print('Testing set mean squared error: {:.4f}'.format(score_mse_test))

    score_r2_test = model.score(x_test, y_test)
    print('Testing set r-squared scores: {:.4f}'.format(score_r2_test))

    # TODO: Save MSE and R-square scores on testing set
    scores_mse_test.append(score_mse_test)
    scores_r2_test.append(score_r2_test)

    # TODO: Set the degrees/orders of polynomials to be 2 and 3 for nonlinear mapping
    degrees_polynomial = [2, 3]

    '''
    Trains and tests linear regression from scikit-learn with scikit-learn polynomial features
    '''
    for degree in degrees_polynomial:

        print('Results of LinearRegression model using scikit-learn order-{} polynomial expansion features'.format(degree))

        # TODO: Initialize polynomial expansion
        poly_transform = skpreprocess.PolynomialFeatures(degree=degree)

        # TODO: Compute the polynomial terms needed for the data
        # Generates x_1^2, x_1 x_2, x_1 x_3, ..., x_d^2
        poly_transform.fit(x_train)

        # TODO: Transform the data by nonlinear mapping
        # Applies all the polynomial terms to the data and augments it to x
        # Computes the values for x_0, x_1, x_2, ..., x_1^2, x_1 x_2, ... x_d^2
        # x_1 = 2, x_2 = 4 : x -> (1, 2, 4, ..., 4, 8, ..., x_d^2)
        x_poly_train = poly_transform.transform(x_train)
        x_poly_val = poly_transform.transform(x_val)
        x_poly_test = poly_transform.transform(x_test)

        # Initialize scikit-learn linear regression model
        model_poly = LinearRegression()

        # TODO: Trains scikit-learn linear regression model using p-order polynomial expansion
        print('Features after polynomial transform order-{}: {}'.format(degree, x_poly_train.shape[1]))
        model_poly.fit(x_poly_train, y_train)

        # TODO: Test model on training set
        predictions_poly_train = model_poly.predict(x_poly_train)

        score_mse_poly_train = skmetrics.mean_squared_error(predictions_poly_train, y_train)
        print('Training set mean squared error: {:.4f}'.format(score_mse_poly_train))

        score_r2_poly_train = model_poly.score(x_poly_train, y_train)
        print('Training set r-squared scores: {:.4f}'.format(score_r2_poly_train))

        # TODO: Save MSE and R-square scores on training set
        scores_mse_train.append(score_mse_poly_train)
        scores_r2_train.append(score_r2_poly_train)

        # TODO: Test model on validation set
        predictions_poly_val = model_poly.predict(x_poly_val)

        score_mse_poly_val = skmetrics.mean_squared_error(predictions_poly_val, y_val)
        print('Validation set mean squared error: {:.4f}'.format(score_mse_poly_val))

        score_r2_poly_val = model_poly.score(x_poly_val, y_val)
        print('Validation set r-squared scores: {:.4f}'.format(score_r2_poly_val))

        # TODO: Save MSE and R-square scores on validation set
        scores_mse_val.append(score_mse_poly_val)
        scores_r2_val.append(score_r2_poly_val)

        # TODO: Test model on testing set
        predictions_poly_test = model_poly.predict(x_poly_test)

        score_mse_poly_test = skmetrics.mean_squared_error(predictions_poly_test, y_test)
        print('Testing set mean squared error: {:.4f}'.format(score_mse_poly_test))

        score_r2_poly_test = model_poly.score(x_poly_test, y_test)
        print('Testing set r-squared scores: {:.4f}'.format(score_r2_poly_test))

        # TODO: Save MSE and R-square scores on testing set
        scores_mse_test.append(score_mse_poly_test)
        scores_r2_test.append(score_r2_poly_test)

    # TODO: Convert each scores to NumPy arrays
    scores_mse_train = np.array(scores_mse_train)
    scores_mse_val = np.array(scores_mse_val)
    scores_mse_test = np.array(scores_mse_test)
    scores_r2_train = np.array(scores_r2_train)
    scores_r2_val = np.array(scores_r2_val)
    scores_r2_test = np.array(scores_r2_test)

    # TODO: Clip each set of MSE scores between 0 and 50
    scores_mse_train = np.clip(scores_mse_train, 0.0, 50.0)
    scores_mse_val = np.clip(scores_mse_val, 0.0, 50.0)
    scores_mse_test = np.clip(scores_mse_test, 0.0, 50.0)

    # TODO: Clip each set of R-squared scores between 0 and 1
    scores_r2_train = np.clip(scores_r2_train, 0.0, 1.0)
    scores_r2_val = np.clip(scores_r2_val, 0.0, 1.0)
    scores_r2_test = np.clip(scores_r2_test, 0.0, 1.0)

    n_experiments = len(scores_mse_train)

    # TODO: Create figure for training, validation and testing scores for different features
    fig = plt.figure()

    # TODO: Create subplot for MSE for training, validation, testing
    ax = fig.add_subplot(1, 2, 1)

    x_values = [range(1, n_experiments + 1)] * n_experiments
    y_values = [scores_mse_train, scores_mse_val, scores_mse_test]
    labels = ['Training', 'Validation', 'Testing']
    colors = ['blue', 'red', 'green']

    # TODO: Plot MSE scores for training, validation, testing sets
    for x, y, label, color in zip(x_values, y_values, labels, colors):
        ax.plot(x, y, marker='o', color=color, label=label)
        ax.legend(loc='best')

    # TODO: Set y limits between 0 and 50, set x limits to 0 to number experiments + 1
    ax.set_ylim([0.0, 50.0])
    ax.set_xlim([0.0, n_experiments + 1])

    # TODO: Set y label to 'MSE', set x label to 'p-degree'
    ax.set_ylabel('MSE')
    ax.set_xlabel('p-degree')

    # TODO: Create subplot for R-square for training, validation, testing

    ax = fig.add_subplot(1, 2, 2)
    x_values = [range(1, n_experiments + 1)] * n_experiments
    y_values = [scores_r2_train,scores_r2_val,scores_r2_test]
    labels = ['Training', 'Validation', 'Testing']
    colors = ['blue', 'red', 'green']

    # TODO: Plot R-squared scores for training, validation, testing sets

    for x,y,label,color in zip(x_values,y_values,labels,colors):
        ax.plot(x, y,marker='o', color = color, label=label)
        ax.legend(loc='best')

    # TODO: Set y limits between 0 and 1, set x limits to 0 to number experiments + 1

    ax.set_ylim([0.0, 1.0])
    ax.set_xlim([0.0, n_experiments + 1])

    # TODO: Set y label to 'R-squared', set x label to 'p-degree'

    ax.set_ylabel('R-squared')
    ax.set_xlabel('p-degree')

    # TODO: Create super title 'Scikit-learn Polynomial Expansion on Training, Validation and Testing Sets'
    plt.suptitle('Scikit-learn Polynomial Expansion on Training, Validation and Testing Sets')

    '''
    Trains and tests linear regression from scikit-learn with our implementation of polynomial features
    '''
    # TODO: Instantiate lists containing the training, validation and testing
    # MSE and R-squared scores obtained from linear regression without nonlinear mapping
    scores_mse_train = [0]
    scores_mse_val = [0]
    scores_mse_test = [0]
    scores_r2_train = [0]
    scores_r2_val = [0]
    scores_r2_test = [0]

    for degree in degrees_polynomial:

        print('Results for LinearRegression model using our implementation of order-{} polynomial expansion features'.format(degree))

        # TODO: Transform the data by nonlinear mapping using our implementation of polynomial expansion
        poly_transform = PolynomialFeatureExpansion(degree=degree)

        # Transform x_train to x_poly_train with p-degree expansion
        x_poly_train = poly_transform.transform(x_train)
        x_poly_val = poly_transform.transform(x_val)
        x_poly_test = poly_transform.transform(x_test)

        # Initialize scikit-learn linear regression model
        model_poly = LinearRegression()

        # TODO: Trains scikit-learn linear regression model using p-order polynomial expansion
        print('Features after polynomial transform order-{}: {}'.format(degree, x_poly_train.shape[1]))
        model_poly.fit(x_poly_train, y_train)

        # TODO: Test model on training set

        prediction_poly_train = model_poly.predict(x_poly_train)

        score_mse_poly_train = skmetrics.mean_squared_error(prediction_poly_train, y_train)
        print('Training set mean squared error: {:.4f}'.format(score_mse_poly_train))

        score_r2_poly_train = model_poly.score(x_poly_train, y_train)
        print('Training set r-squared scores: {:.4f}'.format(score_r2_poly_train))

        # TODO: Save MSE and R-square scores on training set

        scores_mse_train.append(score_mse_poly_train)
        scores_r2_train.append(score_r2_poly_train)

        # TODO: Test model on validation set
        prediction_poly_val = model_poly.predict(x_poly_val)

        score_mse_poly_val = skmetrics.mean_squared_error(prediction_poly_val,y_val)
        print('Validation set mean squared error: {:.4f}'.format(score_mse_poly_val))

        score_r2_poly_val = model_poly.score(x_poly_val,y_val)
        print('Validation set r-squared scores: {:.4f}'.format(score_r2_poly_val))

        # TODO: Save MSE and R-square scores on validation set

        scores_mse_val.append(score_mse_poly_val)
        scores_r2_val.append(score_r2_poly_val)

        # TODO: Test model on testing set
        prediction_poly_test = model_poly.predict(x_poly_test)

        score_mse_poly_test = skmetrics.mean_squared_error(prediction_poly_test,y_test)
        print('Testing set mean squared error: {:.4f}'.format(score_mse_poly_test))

        score_r2_poly_test = model_poly.score(x_poly_test,y_test)
        print('Testing set r-squared scores: {:.4f}'.format(score_r2_poly_test))

        # TODO: Save MSE and R-square scores on testing set
        scores_mse_test.append(score_mse_poly_test)
        scores_r2_test.append(score_r2_poly_test)

    # TODO: Convert each scores to NumPy arrays
    scores_mse_train = np.array(scores_mse_train)
    scores_mse_val = np.array(scores_mse_val)
    scores_mse_test = np.array(scores_mse_test)
    scores_r2_train = np.array(scores_r2_train)
    scores_r2_val = np.array(scores_r2_val)
    scores_r2_test = np.array(scores_r2_test)

    # TODO: Clip each set of MSE scores between 0 and 50
    scores_mse_train = np.clip(scores_mse_train, 0.0, 50.0)
    scores_mse_val = np.clip(scores_mse_val, 0.0, 50.0)
    scores_mse_test = np.clip(scores_mse_test, 0.0, 50.0)

    # TODO: Clip each set of R-squared scores between 0 and 1scores_r2_train = np.clip(scores_r2_train, 0.0, 1.0)
    scores_r2_val = np.clip(scores_r2_val, 0.0, 1.0)
    scores_r2_test = np.clip(scores_r2_test, 0.0, 1.0)
    scores_r2_train = np.clip(scores_r2_train, 0.0, 1.0)
    scores_r2_val = np.clip(scores_r2_val, 0.0, 1.0)
    scores_r2_test = np.clip(scores_r2_test, 0.0, 1.0)

    n_experiments = len(scores_mse_train)

    # TODO: Create figure for training, validation and testing scores for different featuresfig_2 = plt.figure()

    fig_2 = plt.figure()

    # TODO: Create subplot for MSE for training, validation, testing

    ax = fig_2.add_subplot(1, 2, 1)

    x_values = [range(1, n_experiments + 1)] * n_experiments
    y_values = [scores_mse_train,scores_mse_val,scores_mse_test]
    labels = ['Training', 'Validation', 'Testing']
    colors = ['blue', 'red', 'green']

    # TODO: Plot MSE scores for training, validation, testing sets
    for x, y, label, color in zip(x_values, y_values, labels, colors):
        ax.plot(x, y, marker='o', color=color, label=label)
        ax.legend(loc='best')

    # TODO: Set y limits between 0 and 50, set x limits to 0 to number experiments + 1

    ax.set_ylim([0.0, 50.0])
    ax.set_xlim([0.0, n_experiments + 1])

    # TODO: Set y label to 'MSE', set x label to 'p-degree'

    ax.set_ylabel('MSE')
    ax.set_xlabel('p-degree')

    # TODO: Create subplot for R-square for training, validation, testing

    ax = fig_2.add_subplot(1, 2, 2)

    x_values = [range(1, n_experiments + 1)] * n_experiments
    y_values = [scores_r2_train,scores_r2_val,scores_r2_test]
    labels = ['Training', 'Validation', 'Testing']
    colors = ['blue', 'red', 'green']

    # TODO: Plot R-squared scores for training, validation, testing sets

    for x, y, label,color in zip(x_values,y_values,labels,colors):
        ax.plot(x, y, marker='o', color=color, label=label)
        ax.legend(loc='best')

    # TODO: Set y limits between 0 and 1, set x limits to 0 to number experiments + 1

    ax.set_ylim([0.0, 1.0])
    ax.set_xlim([0.0, n_experiments + 1])

    # TODO: Set y label to 'R-squared', set x label to 'p-degree'

    ax.set_ylabel('R-squarred')
    ax.set_xlabel('p-degree')

    # TODO: Create super title 'Our Polynomial Expansion on Training, Validation and Testing Sets'

    plt.suptitle('Our Polynomial Expansion on Training, Validation and Testing Sets')

    plt.show()


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

def solve(m1, m2, std1, std2):
    a = 1 / (2 * std1 ** 2) - 1 / (2 * std2 ** 2)
    b = m2 / (std2 ** 2) - m1 / (std1 ** 2)
    c = m1 ** 2 / (2 * std1 ** 2) - m2 ** 2 / (2 * std2 ** 2) - np.log(std2 / std1)
    return np.roots([a, b, c])

def main():
    N = 100000000  # Number of samples
    bins = 1000  # Number of bins
    np.random.seed(100)  # Ensure fixed seed

    # generate two distributions
    dist1 = np.random.normal(0, 1, N)
    dist2 = np.random.normal(1, np.sqrt(2), N)

    result = solve(0, 1, 1, np.sqrt(2))

    # plot figure
    fig = plt.figure(figsize=(6, 4))
    ax = fig.add_subplot(111)
    y1_, x1_, p1 = ax.hist(dist1, bins=bins, color='red', alpha=0.7, density=True, label=r'p(X|L=1) ~ $N(0, 1)$')
    y2_, x2_, p2 = ax.hist(dist2, bins=bins, color='blue', alpha=0.6, density=True,
                           label=r'p(X|L=2) ~ $N(\mu, \sigma^{2})$')
    ax.legend()
    ax.set_title("Gaussian Class Conditioned Probability")
    ax.set_ylabel("Density")
    ax.set_xlabel("x")
    ax.axvline(x=result[0], color='k', linestyle='--')
    ax.text(result[0] - 3.0, 0.40, r'$lower={0:}$'.format(np.round(result[0], 2)))
    ax.axvline(x=result[1], color='k', linestyle='--')
    ax.text(result[1] - 3.0, 0.40, r'$upper={0:}$'.format(np.round(result[1], 2)))
    plt.savefig('results/q4.png')
    plt.show()

    s = 1
    scaling_factor = 0.5 / s
    fig = plt.figure(figsize=(6, 4))
    ax = fig.add_subplot(111)
    y1, x1 = np.histogram(dist1, bins=bins, density=True)
    y2, x2 = np.histogram(dist2, bins=bins, density=True)
    ax.bar((x1[1:] + x1[:-1]) * .5, y1 * scaling_factor,
           width=(x1[1] - x1[0]), color="red", alpha=0.7, label=r'p(L=1|x) ~ $N(0, 1)$')
    ax.bar((x2[1:] + x2[:-1]) * .5, y2 * scaling_factor,
           width=(x2[1] - x2[0]), color="blue", alpha=0.6, label=r'p(L=2|x) ~ $N(\mu, \sigma^{2})$', )

    ax.legend()
    ax.set_title("Class Posterior Probability")
    ax.set_ylabel("Density")
    ax.set_xlabel("x")
    ax.axvline(x=result[0], color='k', linestyle='--')
    ax.text(result[0] - 1.0, 0.20, r'$lower={0:}$'.format(np.round(result[0], 2)))
    ax.axvline(x=result[1], color='k', linestyle='--')
    ax.text(result[1] - 1.0, 0.20, r'$upper={0:}$'.format(np.round(result[1], 2)))
    plt.savefig('results/q4_posterior.png')
    plt.show()

if __name__ == '__main__':
    dirName = "results"
    if not os.path.exists(dirName):
        try:
            os.mkdir(dirName)
        except OSError:
            print("Creation of the directory %s failed" % dirName)
        else:
            print("Successfully created the directory %s " % dirName)
    else:
        print("Directory already exists.")
    main()


from sympy import Symbol
import matplotlib.pyplot as plt

def geraFuncao(a, b):
    func = 0
    var = Symbol('x')  # Simbolo para montar o polinomio interpolador

    return a + b * var

def calculaReta(a, b, x):
    Y = a + b * x

    return Y

def tabelaQuadradosMinimos(x, y):
    x2 = 0
    xy = 0
    xi = 0
    yi = 0
    for i in range(len(x)):
        x2 += x[i] ** 2
        xy += x[i] * y[i]
        xi += x[i]
        yi += y[i]

    b = ((xi * yi) - (len(x) * xy)) / (xi ** 2 - (len(x) * x2))
    a = (yi - (b * xi)) / len(x)

    return xi, yi, xy, x2, a, b

def calculaDesvio(x, y, a, b):
    D = 0
    for i in range(len(x)):
        D += (y[i] - (a + b * x[i])) ** 2

    return D

def calculaAjuste(x, y, a, b):
    num = 0
    den1 = 0
    den2 = 0
    for i in range(len(x)):
        num += (y[i] - a - b * x[i]) ** 2
        den1 += y[i] ** 2
        den2 += y[i]

    return 1 - num / (den1 - (1 / len(x)) * den2 ** 2)

def mostraGrafico(x, y, a, b, funcao, val):
    vetorXRetaAjuste = [61, 81]
    vetorYRetaAjuste = [calculaReta(a, b, 61), calculaReta(a, b, 81)]

    if val != 'null':
        pontoY = round(a + b * val, 2)
        plt.scatter(val, pontoY, color='yellow')
        plt.text(val, pontoY, f"P({val},{pontoY})", fontsize=8, horizontalalignment='right')

    plt.scatter(x[0], y[0], color='yellow')
    plt.scatter(x[1], y[1], color='yellow')
    plt.scatter(x[2], y[2], color='yellow')
    plt.scatter(x[3], y[3], color='yellow')
    plt.scatter(x[4], y[4], color='yellow')
    # plt.plot(x, y, marker='o', label='Curva real')
    plt.plot(vetorXRetaAjuste, vetorYRetaAjuste, label=f'f(x) = {funcao}')
    plt.title('Ajuste de Curvas')
    plt.xlabel('eixo X')
    plt.ylabel('eixo Y')
    plt.grid(True)
    plt.legend(loc=0)

    plt.show()

def main():
    # x = [0.3, 2.7, 4.5, 5.9, 7.8]
    # y = [1.8, 1.9, 3.1, 3.9, 3.3]

    x = [0.5, 1.2, 2.1, 3.5, 5.4]
    y = [5.1, 3.2, 2.8, 1.0, 0.4]

    xi, yi, xy, x2, a, b = tabelaQuadradosMinimos(x, y)

    funcao = geraFuncao(round(a, 4), round(b, 4))

    resp = float(input('Deseja avaliar um ponto da reta? (1 para SIM) ou (0 para NÃO): '))

    if resp == 1:
        val = float(input('Informe o valor que deseja calcular: '))
    else:
        val = 'null'

    mostraGrafico(x, y, a, b, funcao, val)

    a = 4.3271
    b = -0.7272

    D = calculaDesvio(x, y, a, b)

    r2 = calculaAjuste(x, y, a, b)

    print(f'a = {a}')
    print(f'b = {b}')
    print(f'\nCurva: {funcao}')
    print(f'Valor do desvio = {D}')
    print(f'Qualidade do ajuste = {r2}')

if __name__ == '__main__':
    main()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

###
# Name: Jacob Anabi, Grady Lynch
# Student ID: 2294644, 02297574
# Email: anabi@chapman.edu, grlynch@chapman.edu
# Course: PHYS220/MATH220/CPSC220 Fall 2018
# Assignment: CW09
###

"""
array_calculus.py Module Description:

Computes the derivive of some function f (Df = gradient(x) @ f(x))

Functions:
gradient(x) - computes a gradient matrix based on an array x of domain points
derivative(f, n=100) - computes the derivative of a function func at n domain points

Additonal Notes:
Forward finite difference: (f_i+1 - f_i)/dx
Backward finite difference: (f_i - f_i-1)/dx
Central finite difference: (f_i+1 - f_i-1)/2dx
"""

import numpy as np
from matplotlib import pyplot as plt

def gradient(x):
    """
    gradient(x) function description:

    computes the gradient matrix based on an array x of domain points

    Args:
        x - an array containing the domain points of the function

    Returns:
        2D numpy arry - the gradient matrix, D, which is of the form [[-2, 2, ..., 0][-1, 0, 1, ..., 0], ..., [0, ..., -2, 2]]/(2*dx)
    """
    dx = x[1] - x[0] # dx should be same for each point

    # Creating diagonal arrays to be used in the matrix
    main_diag = np.zeros(len(x)) # main diagonal for the gradient
    pos_diag = np.ones(len(x)-1) # diagonal at index + 1 for the gradient, positive numbers
    neg_diag = np.ones(len(x)-1) # diagonal at index - 1 for the gradient, negative numbers
    main_diag[0], main_diag[-1] = -2, 2 # main diagonal boundarys are -2 and 2 based on forward and backward finite difference
    pos_diag[0] = 2 # positive diagonal front boundary is 2, based on forward finite difference
    neg_diag = neg_diag*-1 # converting each element in the negative diagonal to negative numbers
    neg_diag[-1] = -2 # negative diagonal back boundary is -2, based on backward finite difference

    D = np.diag(main_diag, k=0) + np.diag(pos_diag, k=1) + np.diag(neg_diag, k=-1) # our gradient matrix, D, which is of the form [[-2, 2, ..., 0][-1, 0, 1, ..., 0], ..., [0, ..., -2, 2]]
    return D/(2*dx) # multipling our matrix by the scalar 1/(2*dx)

def derivative(func, xmax=100, n=100, power=1):
    """
    derivative(func, n=100) function description:

    computes the the derivative to some power of some function func for n domain points

    Args:
        func - the function to compute the derivative of
        n - the number of domain points (defaults to 100)
        power - the power to compute the derivative to (defaults to 1) (ex. power = 2 => 2nd derivative)

    Returns:
        1D numpy array - an array of the function func evaluted at n domain points
    """
    x = np.linspace(0, xmax, n, endpoint=True) # domain
    f = np.vectorize(func) # vectorize our function
    Df = np.linalg.matrix_power(gradient(x), power) @ f(x) # matrix multiplication of the gradient(x) to some power and the f(x), our vectorized function
    return Df # a 1D array of each derivative of our function evaluated at each domain point to some power

def plot(x, y, xlabel="", ylabel="", title=""):
    """
    plot(x, y, xlabel="", ylabel="") function description:

    plots a function given a domain and range

    Args:
        x - the domain of values
        y - the range of values
        xlabel - the label of the x-axis (defaults to "")
        ylabel - the label of the y-axis (defaults to "")
    """
    # Plotting
    fig = plt.figure(figsize=(8,6)) # Setting funciton figure size (width, height)
    axes = plt.axes() # Creating function plot axes

    axes.plot(x, y, label=ylabel) # plotting graph
    axes.legend() # axes legend
    plt.xlabel(xlabel) # x-axis label for graph
    plt.ylabel(ylabel) # y-axis label for graph
    plt.title(title) # the title of the graph

    plt.show() # show the graph


from math import ceil
import numpy as np
from scipy import linalg

def lowess(x, y, f, iterations):
    n = len(x)
    r = int(ceil(f * n))
    h = [np.sort(np.abs(x - x[i]))[r] for i in range(n)]
    w = np.clip(np.abs((x[:, None] - x[None, :]) / h), 0.0, 1.0)
    w = (1 - w ** 3) ** 3
    yest = np.zeros(n)
    delta = np.ones(n)
    for iteration in range(iterations):
        for i in range(n):
            weights = delta * w[:, i]
            b = np.array([np.sum(weights * y), np.sum(weights * y * x)])
            A = np.array([[np.sum(weights), np.sum(weights * x)],[np.sum(weights * x), np.sum(weights * x * x)]])
            beta = linalg.solve(A, b)
            yest[i] = beta[0] + beta[1] * x[i]

        residuals = y - yest
        s = np.median(np.abs(residuals))
        delta = np.clip(residuals / (6.0 * s), -1, 1)
        delta = (1 - delta ** 2) ** 2

    return yest

import math
n = 100
x = np.linspace(0, 2 * math.pi, n)
y = np.sin(x) + 0.3 * np.random.randn(n)
f =0.25
iterations=3
yest = lowess(x, y, f, iterations)

import matplotlib.pyplot as plt
plt.plot(x,y,"r.")
plt.plot(x,yest,"b-")

7th SEM/MACHINE LEARNING LABORATORY/10-Locally Weighted Regression Algorithm/LAB10.py


import numpy as np
import pandas as pd

# -----------------------------------
# Regression
# -----------------------------------
# rmse

from sklearn.metrics import mean_squared_error

# y_true are the true values、y_pred are the predictions
y_true = [1.0, 1.5, 2.0, 1.2, 1.8]
y_pred = [0.8, 1.5, 1.8, 1.3, 3.0]

rmse = np.sqrt(mean_squared_error(y_true, y_pred))
print(rmse)
# 0.5532

# -----------------------------------
# Binary classification
# -----------------------------------
# Confusion matrix

from sklearn.metrics import confusion_matrix

# True values and predicted values are binary, i.e. either 0 or 1
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]

tp = np.sum((np.array(y_true) == 1) & (np.array(y_pred) == 1))
tn = np.sum((np.array(y_true) == 0) & (np.array(y_pred) == 0))
fp = np.sum((np.array(y_true) == 0) & (np.array(y_pred) == 1))
fn = np.sum((np.array(y_true) == 1) & (np.array(y_pred) == 0))

confusion_matrix1 = np.array([[tp, fp],
                              [fn, tn]])
print(confusion_matrix1)
# array([[3, 1],
#        [2, 2]])

# Can also be created using the confusion_matrix() function from scikit-learn's metrics, but
# be aware that the arrangement of the confusion matrix elements may be different
confusion_matrix2 = confusion_matrix(y_true, y_pred)
print(confusion_matrix2)
# array([[2, 1],
#        [2, 3]])

# -----------------------------------
# accuracy

from sklearn.metrics import accuracy_score

# True values and predicted values are binary, i.e. either 0 or 1
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]
accuracy = accuracy_score(y_true, y_pred)
print(accuracy)
# 0.625

# -----------------------------------
# logloss

from sklearn.metrics import log_loss

# True values are binary (0 or 1), predicted values are probabilities
y_true = [1, 0, 1, 1, 0, 1]
y_prob = [0.1, 0.2, 0.8, 0.8, 0.1, 0.3]

logloss = log_loss(y_true, y_prob)
print(logloss)
# 0.7136

# -----------------------------------
# Multi-class classification
# -----------------------------------
# multi-class logloss

from sklearn.metrics import log_loss

# True values are 3-class classifiers, predicted values are probabilities for each class
y_true = np.array([0, 2, 1, 2, 2])
y_pred = np.array([[0.68, 0.32, 0.00],
                   [0.00, 0.00, 1.00],
                   [0.60, 0.40, 0.00],
                   [0.00, 0.00, 1.00],
                   [0.28, 0.12, 0.60]])
logloss = log_loss(y_true, y_pred)
print(logloss)
# 0.3626

# -----------------------------------
# Multi-label classification
# -----------------------------------
# mean_f1, macro_f1, micro_f1

from sklearn.metrics import f1_score

# For calculating performance metric of multi-label classification, it is easier to handle the true / predicted values as binary matrices of record x class
# True values - [[1,2], [1], [1,2,3], [2,3], [3]]
y_true = np.array([[1, 1, 0],
                   [1, 0, 0],
                   [1, 1, 1],
                   [0, 1, 1],
                   [0, 0, 1]])

# Predicted values - [[1,3], [2], [1,3], [3], [3]]
y_pred = np.array([[1, 0, 1],
                   [0, 1, 0],
                   [1, 0, 1],
                   [0, 0, 1],
                   [0, 0, 1]])

# mean_f1 is the mean of the F1-scores for each record
mean_f1 = np.mean([f1_score(y_true[i, :], y_pred[i, :]) for i in range(len(y_true))])

# macro_f1 is the mean of the F1-scores for each class
n_class = 3
macro_f1 = np.mean([f1_score(y_true[:, c], y_pred[:, c]) for c in range(n_class)])

# micro-f1 is the F1-score calculate using the true/predicted values for each record-class pair
micro_f1 = f1_score(y_true.reshape(-1), y_pred.reshape(-1))

print(mean_f1, macro_f1, micro_f1)
# 0.5933, 0.5524, 0.6250

# Can also be calculated using a scikit-learn function
mean_f1 = f1_score(y_true, y_pred, average='samples')
macro_f1 = f1_score(y_true, y_pred, average='macro')
micro_f1 = f1_score(y_true, y_pred, average='micro')

# -----------------------------------
# Multi-class classification with ordered classes
# -----------------------------------
# quadratic weighted kappa

from sklearn.metrics import confusion_matrix, cohen_kappa_score

# Function for calculating quadratic weighted kappa
def quadratic_weighted_kappa(c_matrix):
    numer = 0.0
    denom = 0.0

    for i in range(c_matrix.shape[0]):
        for j in range(c_matrix.shape[1]):
            n = c_matrix.shape[0]
            wij = ((i - j) ** 2.0)
            oij = c_matrix[i, j]
            eij = c_matrix[i, :].sum() * c_matrix[:, j].sum() / c_matrix.sum()
            numer += wij * oij
            denom += wij * eij

    return 1.0 - numer / denom

# y_true is the true class list, y_pred is the predicted class list
y_true = [1, 2, 3, 4, 3]
y_pred = [2, 2, 4, 4, 5]

# Calculate the confusion matrix
c_matrix = confusion_matrix(y_true, y_pred, labels=[1, 2, 3, 4, 5])

# Calculate quadratic weighted kappa
kappa = quadratic_weighted_kappa(c_matrix)
print(kappa)
# 0.6153

# Can also be calculated using a scikit-learn function
kappa = cohen_kappa_score(y_true, y_pred, weights='quadratic')

# -----------------------------------
# Recommendation
# -----------------------------------
# MAP@K

# K=3, with 5 records and 4 class types
K = 3

# True values for each record
y_true = [[1, 2], [1, 2], [4], [1, 2, 3, 4], [3, 4]]

# Predicted values for each record - as K=3, usually predict order of 3 records for each class
y_pred = [[1, 2, 4], [4, 1, 2], [1, 4, 3], [1, 2, 3], [1, 2, 4]]

# Function to calculate the average precision for each record
def apk(y_i_true, y_i_pred):
    # Length of y_pred must be less than or equal to K, and all elements must be unique
    assert (len(y_i_pred) <= K)
    assert (len(np.unique(y_i_pred)) == len(y_i_pred))

    sum_precision = 0.0
    num_hits = 0.0

    for i, p in enumerate(y_i_pred):
        if p in y_i_true:
            num_hits += 1
            precision = num_hits / (i + 1)
            sum_precision += precision

    return sum_precision / min(len(y_i_true), K)

# Function for calculating MAP@K
def mapk(y_true, y_pred):
    return np.mean([apk(y_i_true, y_i_pred) for y_i_true, y_i_pred in zip(y_true, y_pred)])

# Calculate MAP@K
print(mapk(y_true, y_pred))
# 0.65

# Even if the number of true values is the same, if the order is different then the score will be different
print(apk(y_true[0], y_pred[0]))
print(apk(y_true[1], y_pred[1]))
# 1.0, 0.5833


import numpy as np

X = np.array(([2, 9], [1, 5], [3, 6])) # Hours Studied, Hours Slept
y = np.array(([92], [86], [89])) # Test Score

y = y/100 # max test score is 100

# Sigmoid Function
def sigmoid(x): #this function maps any value between 0 and 1
    return 1/(1 + np.exp(-x))

# Derivative of Sigmoid Function
def derivatives_sigmoid(x):
    return x * (1 - x)

# Variable initialization
epoch = 10000 # Setting training iterations
lr = 0.1 #Setting learning rate
inputlayer_neurons = 2 # number of features in data set
hiddenlayer_neurons = 3 # number of hidden layers neurons
output_neurons = 1 # number of neurons of output layer

# weight and bias initialization
weight_hidden = np.random.uniform(size=(inputlayer_neurons, hiddenlayer_neurons))
# bias matrix to the hidden layer
bias_hidden = np.random.uniform(size=(1, hiddenlayer_neurons))
# weight matrix to the output layer
weight_output = np.random.uniform(size=(hiddenlayer_neurons, output_neurons))
# matrix to the output layer
bias_output = np.random.uniform(size=(1, output_neurons))

for i in range(epoch):
    # Forward Propogation
    hinp1 = np.dot(X, weight_hidden)
    hinp = hinp1 + bias_hidden # bias_hidden GRADIENT Descent
    hlayer_activation = sigmoid(hinp)

    outinp1 = np.dot(hlayer_activation, weight_output)
    outinp = outinp1 + bias_output
    output = sigmoid(outinp)

    # Backpropagation
    # Compare prediction with actual output and calculate the gradient of error
    # (Actual – Predicted)
    EO = y - output
    # Compute the slope/gradient of hidden and output layer neurons
    outgrad = derivatives_sigmoid(output)

    # Compute change factor(delta) at output layer, dependent on the gradient
    # of error multiplied by the slope of output layer activation
    d_output = EO * outgrad

    # At this step, the error will propagate back into the network which
    # means error at hidden layer. we will take the dot product of output
    # layer delta with weight parameters of edges between the hidden and
    # output layer (weight_hidden.T).
    EH = d_output.dot(weight_output.T)

    # how much hidden layer weight contributed to error
    hiddengrad = derivatives_sigmoid(hlayer_activation)
    d_hiddenlayer = EH * hiddengrad

    # update the weights
    # dot product of nextlayererror and currentlayerop
    weight_output += hlayer_activation.T.dot(d_output) * lr
    bias_output += np.sum(d_output, axis=0, keepdims=True) * lr

    weight_hidden += X.T.dot(d_hiddenlayer) * lr
    bias_hidden += np.sum(d_hiddenlayer, axis=0, keepdims=True) * lr

print("Input:\n" + str(X))
print("Actual Output:\n" + str(y))
print("Predicted Output:\n", output)


import numpy as np
import math
from scipy.stats import skew
from scipy.stats import kurtosis

def zero_cross_rate(series):
    """
    How often the signal changes sign (+/-)
    """
    series_mean = np.mean(series)
    series = [v-series_mean for v in series]
    zero_cross_count = (np.diff(np.sign(series)) != 0).sum()
    # print('zero_cross_count', zero_cross_count)
    return zero_cross_count / len(series)

def compute_statistical_features(data):
    mean = np.mean(data)
    median = np.median(data)
    std = np.std(data)
    skewness = skew(data)
    kurt = kurtosis(data)
    power = np.mean([v * v for v in data])
    zc = zero_cross_rate(data)
    return [mean, median, std, skewness, kurt, power, zc]


import numpy as np
import matplotlib.pyplot as plt
from IPython.display import clear_output

def Tfunc(n_row,n_col):
    T=np.zeros([n_row,n_col])
    for x_i in range(n_row):
        for y_j in range(n_col):

            if y_j==0:
                T[x_i,y_j] = T[x_i,y_j+1]

            elif y_j==n_col-1:
                T[x_i,y_j] = T[x_i,y_j-1]

            if x_i==0:
                T[x_i,y_j]=0

            if x_i==n_row-1:
                T[x_i,y_j]=1
    return (T)

def Tmap(T,iters,Errors):
    fig1 = plt.figure(1)
    ax1 = fig1.add_subplot(121)
    ax2 = fig1.add_subplot(122)

    clear_output(wait=True)

    ax1.clear()
    heatmap = ax1.pcolor(T, cmap=plt.cm.gist_heat)
    plt.colorbar(heatmap,ax=ax1)
    ax1.set_xlabel('$Y_{j}$')
    ax1.set_ylabel('$X_{i}$')
    ax1.set_title("Temperature profile")

    ticks=[round(i/mesh,2) for i in ax1.get_xticks()]

    ax1.set_xticklabels(ticks)
    ax1.set_yticklabels(ticks)

    ax2.clear()
    ax2.plot(iters,Errors)
    ax2.set_xlabel('Iteration')
    ax2.set_ylabel('Error')
    ax2.set_title("Convergence History")

    title='method = ' + method+'  mesh size= '+str(mesh)+' w= '+str(w)
    fig1.suptitle(title)

    plt.show()

def FiniteDiff(Temp,mesh,method,w):

    n_col = mesh
    n_row = mesh

    dy=(1/n_col)
    dx=(1/n_row)

    a=(dx**2)
    b=(dy**2)

    T=Temp.copy()

    if method == 'Jacobi':
        Tbuffer=T.copy()

    else:
        Tbuffer=T

    Tnew=T.copy()

    for x_i in range(1,n_row-1):

        for y_j in range(1,n_col-1):

            Tbuffer[x_i,y_j]=((100*a*b-b*(T[x_i+1,y_j]+T[x_i-1,y_j])-a*(T[x_i,y_j+1]+T[x_i,y_j-1]))/(-2*(a+b)))
            if method =='SOR':
                Tbuffer[x_i,y_j]=Tnew[x_i,y_j]+w*(Tbuffer[x_i,y_j]-Tnew[x_i,y_j])
            else:
                continue

    T=Tnew.copy()
    Tnew=Tbuffer.copy()

    for x_i in range(n_row):
        for y_j in range(n_col):

            if y_j==0:
                Tnew[x_i,y_j] = Tnew[x_i,y_j+1]

            elif y_j==n_col-1:
                Tnew[x_i,y_j] = Tnew[x_i,y_j-1]

            if x_i==0:
                Tnew[x_i,y_j]=0

            if x_i==n_row-1:
                Tnew[x_i,y_j]=1

    A=abs(Tnew-T)

    maxError=np.amax(A)

    return (Tnew,A,maxError)

def Temp_Profile(mesh,method,w=1):

    n_col = mesh
    n_row = mesh

    dy=(1/n_col)
    dx=(1/n_row)

    a=(dx**2)
    b=(dy**2)

    tolerance=0.001

    iterations=[]
    Errors=[]

    iteration=1

    y=[j*dy for j in range(n_col)]
    x=[i*dx for i in range(n_row)]

    Temp=Tfunc(n_row,n_col)

    Tmap(Temp,iterations,Errors)

    T_Jacobi,A,Error=FiniteDiff(Temp,mesh,method,w)

    Errors.append(Error)
    iterations.append(iteration)

    Tmap(T_Jacobi,iterations,Errors)

    print('Initial Error = ', Error,'\n\n')

    while Error>tolerance:

        T_Jacobi,A,Error=FiniteDiff(T_Jacobi,mesh,method,w)

        iteration+=1

        Errors.append(Error)
        iterations.append(iteration)

        Tmap(T_Jacobi,iterations,Errors)

        print('iter = ',iteration,'\tError = ',Error)

#testing
'''

Meshes=[9,17,33]
Methods=['Jacobi','Gauss','SOR']

for mesh in Meshes:
    for method in Methods:
        print('**********mesh = ',mesh,'\tmethod = ',method,'**********\n\n')
        if method=='SOR':
            for w in [0.5,1.5,2]:
                print('***W = ',w,' ***')
                Temp_Profile(mesh,method,w)
        else:
            Temp_Profile(mesh,method)

'''


# solve a 2-d Poisson equation by differentiating the discretized Poisson
# equation and then substituting in the inverse Fourier transform and solving
# for the amplitudes in Fourier space.
#
# This is the way that Garcia and NR do it.
#
# Note: we need a periodic problem for an FFT
#
# M. Zingale (2013-04-02)

import pylab
import numpy
import math

# the analytic solution
def true(x,y):
    pi = math.pi
    return numpy.sin(2.0*pi*x)**2*numpy.cos(4.0*pi*y) + \
        numpy.sin(4.0*pi*x)*numpy.cos(2.0*pi*y)**2

# the righthand side
def frhs(x,y):
    pi = math.pi
    return 8.0*pi**2*numpy.cos(4.0*pi*y)*(numpy.cos(4.0*pi*x) -
                                          numpy.sin(4.0*pi*x)) - \
           16.0*pi**2*(numpy.sin(4.0*pi*x)*numpy.cos(2.0*pi*y)**2 +
                       numpy.sin(2.0*pi*x)**2 * numpy.cos(4.0*pi*y))

# create the domain
xmin = 0.0
xmax = 1.0

ymin = 0.0
ymax = 1.0

Nx = 64
Ny = 64

dx = (xmax - xmin)/Nx
dy = (ymax - ymin)/Ny

x = (numpy.arange(Nx) + 0.5)*dx
y = (numpy.arange(Ny) + 0.5)*dy

x2d = numpy.repeat(x, Ny)
x2d.shape = (Nx, Ny)

y2d = numpy.repeat(y, Nx)
y2d.shape = (Ny, Nx)
y2d = numpy.transpose(y2d)

# create the RHS
f = frhs(x2d, y2d)
print f

# compatibility conditions require that the RHS sum to zero
print "sum of RHS: ", numpy.sum(f)

print x2d.shape

# FFT of RHS
F = numpy.fft.fft2(f)

# get the wavenumbers -- we need these to be physical, so multiply by Nx
kx = Nx*numpy.fft.fftfreq(Nx)
ky = Ny*numpy.fft.fftfreq(Ny)

# make 2-d arrays for the wavenumbers
kx2d = numpy.repeat(kx, Ny)
kx2d.shape = (Nx, Ny)

ky2d = numpy.repeat(ky, Nx)
ky2d.shape = (Ny, Nx)
ky2d = numpy.transpose(ky2d)

# here the FFT frequencies are in the order 0 ... N/2-1, -N/2, ...
# the 0 component is not a physical frequency, but rather it is the DC
# signal.  Don't mess with it, since we'll divide by zero
oldDC = F[0,0]
F = 0.5*F*dx*dx/(numpy.cos(2.0*math.pi*kx2d/Nx) +
                 numpy.cos(2.0*math.pi*ky2d/Ny) - 2.0)# + 1.e-20)

F[0,0] = oldDC

# transform back to real space
fsolution = numpy.real(numpy.fft.ifft2(F))

#pylab.subplot(121)
#pylab.imshow(fsolution, origin="lower", interpolation="nearest")
#pylab.colorbar()

#pylab.subplot(122)
pylab.imshow(true(x2d,y2d))
pylab.colorbar()

#pylab.tight_layout()

pylab.savefig("poissonFFT.png")

# error
print Nx, Ny, numpy.sqrt(dx*dx*numpy.sum( ( (fsolution - true(x2d,y2d))**2).flat))


from sympy import symbols, integrate, Rational, lambdify, sqrt, solve
import matplotlib.pyplot as plt
import numpy as np

def plot_fun( fun, name, col ):
	x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
	y_vals = fun( x_vals )
	plt.plot( x_vals, y_vals, label = name, color = col )

# Suppose a company wants to introduce a new machine that will produce a rate of annual savings (in dollars) given by the function Upper S'(x)
# where x is the number of years of operation of the machine, while producing a rate of annual costs (in dollars) given by the function C'(x)
x = symbols( 'x', positive = True )

dS = 212 - x**2
dC = x**2 + Rational( 6, 5 )*x

P = dS - dC

solve( P, x )

# What are the net total savings during the first year of use of the machine?
savings = integrate( P, ( x, 0, 1 ) ).evalf()
round( savings )

#  What are the net total savings over the entire period of use of the machine
savings = integrate( P, ( x, 0, 10 ) ).evalf()
round( savings )

g_xlim = [ 1, 30 ]
g_ylim = [ -5, 15 ]

lam_s = lambdify( x, integrate( dS, x ), np )
plot_fun( lam_s, 'S', 'B' )

lam_c = lambdify( x, integrate( dC, x ), np )
plot_fun( lam_c, 'C', 'Orange' )

lam_p = lambdify( x, integrate( P, x ), np )
plot_fun( lam_p, 'P', 'G' )

plt.legend()
plt.show()


import math
import random
from numba import njit
import numpy as np

@njit
def findPi(num_samples, trials_per_sample):
  samples = np.zeros((num_samples))
  mean = 0.0

  for sample in range(num_samples):
    samples[sample] = integrateCircle(trials_per_sample)

  mean = samples.sum() / samples.shape[0]

  std_dev = getStdDev(mean, samples)

  return mean, std_dev

@njit
def getStdDev(mean, samples):
  variance = 0

  for sample in samples:
    variance += (sample - mean) ** 2

  variance /= samples.shape[0]
  return math.sqrt(variance)

@njit
def integrateCircle(points_placed):
  counter = 0

  for trial in range(points_placed):
    y_pos = random.random()
    x_pos = random.random()

    dist = y_pos ** 2 + x_pos ** 2
    if dist <= 1:
      counter += 1

  return 4.0 * counter / points_placed

res = [0, 0]
for i in range(20):
  res1, res2 = findPi(1000, 50000)
  res[0] += res1 / 20
  res[1] += res2 / 20
print(res)


# Bench mark function 16
# Six-Hump Camel-Back Function
# HW dimension: 2
# Min = -1.031628453489877
# Range [-5,5]
# Reference: https://al-roomi.org/benchmarks/unconstrained/2-dimensions/23-six-hump-camel-back-function

import math
import numpy as np

name = "F16"
l_bound = -5
u_bound = 5
dim = 2
opt = -1.031628453489877

def func(X):
    t1 = 4 * X[0]**2
    t2 = -1 * 2.1 * X[0]**4
    t3 = X[0]**6 / 3
    t4 = np.prod(X)
    t5 = -1 * 4 * X[1]**2
    t6 = 4 * X[1]**4
    result = t1 + t2 + t3 + t4 + t5 + t6
    return result

if __name__ == '__main__':
    X = np.arange(2)  # Dimension must be 2, in [-5 to 5]
    print(X)
    result = func(X)
    print(result)


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import numpy as np
import helper

def null_vector():
  # MISSING: Create a vector of size 10 and of type np.float32 full of zeros.
  return np.zeros(10, dtype=np.float32)

  return np.array([])

def chess():
  # MISSING: Create a 8x8 matrix and fill it with a checkerboard pattern
  # starting with 1 in the upper left and finishing with 0.
  # The matrix type should be np.int32.
  z = np.zeros((8, 8), dtype=np.int32)
  z[::2, ::2] = 1
  z[1::2, 1::2] = 1
  return z

  return np.array([])

def polar(a):
  # MISSING: "a" is a Nx2 matrix representing cartesian coordinates,
  # convert them to polar coordinates.
  # This function should return a Nx2 matrix where:
  # - the first column is the radius.
  #  -the second column is the angle.
  x = a[:, 0].reshape((-1, 1))
  y = a[:, 1].reshape((-1, 1))
  return np.concatenate([np.sqrt(x ** 2 + y ** 2), np.arctan2(y, x)], axis=1)

  return np.array([])

def cap(a, b):
  # MISSING: Cap all elements of "a" at "b" (i.e., max(a_{i,j}) <= b).
  # BONUS: Make sure to do so without modifying the input arguments.
  c = a.copy()
  c[c > b] = b
  return c

  return np.array([])

def moving_average(a):
  # MISSING: Compute the moving average of the elements of "a" over a
  # window of size 3.
  return np.convolve(a, np.ones(3) / 3., mode='valid')

  return np.array([])

def main():
  helper.check_solution(null_vector, [0] * 10)
  helper.check_solution(chess, [[1, 0, 1, 0, 1, 0, 1, 0],
                                [0, 1, 0, 1, 0, 1, 0, 1],
                                [1, 0, 1, 0, 1, 0, 1, 0],
                                [0, 1, 0, 1, 0, 1, 0, 1],
                                [1, 0, 1, 0, 1, 0, 1, 0],
                                [0, 1, 0, 1, 0, 1, 0, 1],
                                [1, 0, 1, 0, 1, 0, 1, 0],
                                [0, 1, 0, 1, 0, 1, 0, 1]])
  helper.check_solution(polar, [[0.70710678118, np.pi / 4.],
                                [0.70710678118 * 2, np.pi / 4.]],
                        np.array([[.5, .5], [1., 1.]], np.float32),
                        approx=True)
  helper.check_solution(cap, [1, 2, 2], np.array([1, 2, 3]), 2)
  helper.check_solution(cap, [2, 1, 2], np.array([3., 1., 3.]), 2)
  helper.check_solution(moving_average, [2, 3], np.array([1., 2., 3., 4.]))
  helper.check_solution(moving_average, [2], np.array([2., 2., 2.]))

if __name__ == '__main__':
  main()


import numpy as np

def softplus(x):
    """
    log(1 + e^x)

    np.exp を評価してから log をとるため実際の値としては小さくても
    計算機的にはオーバーフローをしてしまうケースがある
    """
    return np.log(1 + np.exp(x))

def softplus2(x):
    """
    max(x, 0) + log(1 + e^{-|x|})

    オーバーフローをしないように同値な式に書き換え
    """
    return max(0, x) + np.log(1 + np.exp(-abs(x)))


from typing import Callable

import numpy as np

def midpoint(
    func: Callable[..., float], a: float, b: float, eps: float = 0.0001, *args, **kwargs
) -> float:

    """
    Aturan titik tengah berfungsi mengoptimasi metode segiempat
    Dengan aturan ini, metode segiempat membagi domain integrasi
    sebanyak n buah dan menjumlahkan seluruh luas segi empat dengan dimensi
    (a + b)/n * f(x), dimana x adalah titik tengah segiempat

    Parameter:
    f   = fungsi input
    a   = batas bawah integrasi
    b   = batas atas integrasi
    eps = error relatif maksimal

    >>> midpoint(lambda x: x**3, 0, 1)
    0.25503446882812497
    >>> midpoint(lambda x: x**10, 0, 1)
    0.09396302982674617
    """
    try:
        n = 100
        x = np.linspace(a, b, n)
        dx = x[1] - x[0]
        L0 = sum([dx * func(i + dx / 2, *args, **kwargs) for i in x])
        err = 1
        while err > eps:
            n += 1
            x = np.linspace(a, b, n)
            dx = x[1] - x[0]
            L1 = sum([dx * func(i + dx / 2, *args, **kwargs) for i in x])
            err = np.abs(L1 - L0) / np.abs(L1)
            L0 = L1
    except Exception:
        raise RuntimeError("Integrasi gagal, pastikan fungsi anda benar!")
    return L1

if __name__ == "__main__":

    def f(x: float) -> float:
        """
        Test Function
        """
        return x ** 3

    print(midpoint(f, 0, 1))

    import doctest

    doctest.testmod()


# based on http://cs231n.github.io/python-numpy-tutorial/

# If you want to use this material in your own trainning please let me know.
#%%

import numpy as np

x = np.array([1, 2])  # Let numpy choose the datatype
print( x.dtype)         # Prints "int64"

x = np.array([1., 2.])  # Let numpy choose the datatype
print( x.dtype)             # Prints "float64"

x = np.array([1, 127], dtype=np.int8)  # Force a particular datatype
print(x.dtype)

#%%
x = np.array([1, 128], dtype=np.int8)  # Force a particular datatype
print(x)

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

#%% Elementwise sum; both produce the array
# [[ 6.0  8.0]
#  [10.0 12.0]]
print(x + y)
print(np.add(x, y))

#%% Elementwise difference; both produce the array
# [[-4.0 -4.0]
#  [-4.0 -4.0]]
print(x - y)

print(np.subtract(x, y))

#%% Elementwise product; both produce the array
# [[ 5.0 12.0]
#  [21.0 32.0]]
print(x * y)
print(np.multiply(x, y))

#%% Elementwise division; both produce the array
# [[ 0.2         0.33333333]
#  [ 0.42857143  0.5       ]]
print(x / y)
print(np.divide(x, y))

#%%
# list of math functions https://numpy.org/doc/stable/reference/routines.math.html

#%% Elementwise square root; produces the array
# [[ 1.          1.41421356]
#  [ 1.73205081  2.        ]]
print( np.sqrt(x))

#%% vector matrix operation
x = np.array([[1,2],[3,4]])
y = np.array([[5,6],[7,8]])

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

#%% Inner product of vectors; both produce 219
print( v.dot(w))
print( np.dot(v, w))

# Matrix / vector product; both produce the rank 1 array [29 67]
print(x.dot(v))
print(np.dot(x, v))

# Matrix / matrix product; both produce the rank 2 array
# [[19 22]
#  [43 50]]
print(x.dot(y))
print(np.dot(x, y))

#%%
x = np.array([[1,2],[3,4]])

print(x)
print(np.sum(x))  # Compute sum of all elements; prints "10"
print(np.sum(x, axis=0))# Compute sum of each column; prints "[4 6]"
print(np.sum(x, axis=1))  # Compute sum of each row; prints "[3 7]"

#%%
print(np.mean(x))  # Compute mean of all elements;
print(np.mean(x, axis=0))# Compute mean of each column;
print(np.mean(x, axis=1))  # Compute mean of each row;

#%%
print(np.std(x))          # Compute standard derivation of all elements;
print(np.std(x, axis=0))  # Compute standard derivation of each column;
print(np.std(x, axis=1))  # Compute standard derivation of each row;

#%%
x = np.array([[1,2], [3,4]])
print(x)    # Prints "[[1 2]
           #          [3 4]]"
print(x.T)  # Prints "[[1 3]
           #          [2 4]]"

#%% Note that taking the transpose of a rank 1 array does nothing:
v = np.array([1,2,3])
print(v)  # Prints "[1 2 3]"
print(v.T)  # Prints "[1 2 3]"

# generate linear map from (-pi/2, pi/2)
x = np.linspace(-np.pi/2,np.pi/2,10)

# calculate sine and cosine in one step, for whole array
print(np.sin(x))

print(np.cos(x))

# [-5, ... , 5]
x = np.arange(-5,5)

#%% calcultae e^(-5), .... , e^5
print(np.exp(x))

#%% x^2, compute x to power of 2
print(np.power(x,2))

# using python operators compute each element of matrix x to power of 2
print(x**2)

# calculate 2^-5 ... 2^5, !!! error, integer not allowed negative exponent
print(np.power(2,x))

# now everything works as expected :)
x = np.arange(-5,5, dtype=np.float)

# natural logarithm, look at minus values!!
print(np.log(x))

xx = np.power(2,x)
print(xx)

# logarithm base 2
print(np.log2(xx))


"""Solving  a System of ODEs"""

"""Euler's Method""" """Approximation of functions/differential equations""" """NOT ROOT FINDING"""
# importing modules
import numpy as np
import matplotlib.pyplot as plt
import math
# ------------------------------------------------------
# functions that returns dy/dx
# i.e. the equation we want to solve: dy_j/dx = f_j(x,y_j)
# (j=[1,2] in this case)
def model(x,y_1,y_2):
    f_1 = -0.5 * y_1
    f_2 = 4.0 - 0.3 * y_2 - 0.1 * y_1
    return [f_1 , f_2]
# ------------------------------------------------------
# ------------------------------------------------------
# initial conditions
x0 = 0
y0_1 = 4
y0_2 = 6
# total solution interval
x_final = 2
# step size
h = 0.00001
# ------------------------------------------------------
# ------------------------------------------------------
# Euler method
# number of steps
n_step = math.ceil(x_final/h)
# Definition of arrays to store the solution
y_1_eul = np.zeros(n_step+1)
y_2_eul = np.zeros(n_step+1)
x_eul = np.zeros(n_step+1)
# Initialize first element of solution arrays
# with initial condition
y_1_eul[0] = y0_1
y_2_eul[0] = y0_2
x_eul[0] = x0
# Populate the x array
for i in range(n_step):
    x_eul[i+1] = x_eul[i] + h
# Apply Euler method n_step times
for i in range(n_step):
    # compute the slope using the differential equation
    [slope_1 , slope_2] = model(x_eul[i],y_1_eul[i],y_2_eul[i])
    # use the Euler method
    y_1_eul[i+1] = y_1_eul[i] + h * slope_1
    y_2_eul[i+1] = y_2_eul[i] + h * slope_2
print(f'Values of y_1: {y_1_eul[i]} and y_2: {y_2_eul[i]}')

"""Solve IVP METHOD""" """INTEGRATION"""
# importing modules
import numpy as np
import matplotlib.pyplot as plt
import math
from scipy.integrate import solve_ivp
# ------------------------------------------------------
# functions that returns dy/dx
# i.e. the equation we want to solve:
# dy_j/dx = f_j(x,y_j) (j=[1,2] in this case)
def model(x,y):
    y_1 = y[0]
    y_2 = y[1]
    f_1 = -0.5 * y_1
    f_2 = 4.0 - 0.3 * y_2 - 0.1 * y_1
    return [f_1 , f_2]
# ------------------------------------------------------
# ------------------------------------------------------
# initial conditions
x0 = 0
y0_1 = 4
y0_2 = 6
# total solution interval
x_final = 2
# step size
# not needed here. The solver solve_ivp
# will take care of finding the appropriate step
# -------------------------------------------------
# ------------------------------------------------------
# Apply solve_ivp method
y = solve_ivp(model, [0 , x_final] ,[y0_1 , y0_2])
# ------------------------------------------------------
# ------------------------------------------------------
# plot results
plt.plot(y.t,y.y[0,:] , 'b.-',y.t,y.y[1,:] , 'r-')
plt.xlabel('x')
plt.ylabel('y_1(x), y_2(x)')
plt.show()
# ------------------------------------------------------
# ------------------------------------------------------
# print results in a text file (for later use if needed)
file_name= 'output.dat'
f_io = open(file_name,'w')
n_step = len(y.t)
for i in range(n_step):
    s1 = str(i)
    s2 = str(y.t[i])
    s3 = str(y.y[0,i])
    s4 = str(y.y[1,i])
    s_tot = s1 + ' ' + s2 + ' ' + s3 + ' ' + s4
    f_io.write(s_tot + '\n')
f_io.close()
# ------------------------------------------------------

"""Implicit Euler Method""" """MAGIC BULLET FOR STIFF SYSTEMS"""

# importing modules
import numpy as np
import matplotlib.pyplot as plt
import math
# ------------------------------------------------------
# inputs
# functions that returns dy/dx
# i.e. the equation we want to solve: dy/dx = - y
def model2(y,x):
    dydx = -1000.0*y + 3000.0 - 2000.0*math.exp(-x)
    return dydx
# initial conditions
x0 = 0
y0 = 0
# total solution interval
x_final = 0.3
# step size
h = 0.05
# ------------------------------------------------------
# Secant method (a very compact version)
def secant_2(f, a, b, iterations):
    for i in range(iterations):
        c = a - f(a)*(b - a)/(f(b) - f(a))
        if abs(f(c)) < 1e-13:
            return c
        a = b
        b = c
    return c
# ------------------------------------------------------
# Euler implicit method
# number of steps
n_step = math.ceil(x_final/h)
# Definition of arrays to store the solution
y_eul = np.zeros(n_step+1)
x_eul = np.zeros(n_step+1)
# Initialize first element of solution arrays
# with initial condition
y_eul[0] = y0
x_eul[0] = x0
# Populate the x array
for i in range(n_step):
    x_eul[i+1] = x_eul[i] + h
# Apply implicit Euler method n_step times
for i in range(n_step):
    F = lambda y_i_plus_1: y_eul[i] + model2(y_i_plus_1,x_eul[i+1])*h - y_i_plus_1
    y_eul[i+1] = secant_2(F, y_eul[i],1.1*y_eul[i]+10**-3,10)
    print(y_eul[i+1])


#!python
# -*- coding: utf-8 -*-#
"""
:Title: Multivariate Linear Regression.

@author: Bhishan Poudel

@date: Sep 22, 2017

@email: bhishanpdl@gmail.com

The cost function is given by

.. math::

  J(w) = \\frac{1}{2N} \sum_{n=1}^N (h(x_n,w) - t_n)^2

Minimizing the cost function w.r.t. w gives the solution:

.. math::

  w = np.linalg.lstsq(X1,t)[0]
"""
# Imports
import argparse
import sys
import numpy as np
from matplotlib import pyplot as plt
import numpy.polynomial.polynomial as poly
from numpy.core.umath_tests import inner1d
from numpy.linalg import norm,lstsq,inv

# for univariate multivariate comparison
from univariate import univariate_reg

# checking
#

# Read data matrix X and labels t from text file.
def read_data(infile):
    """Read the datafile.

    Args:
      infile (str): path to datafile

    """
    # data = np.loadtxt(infile)
    data = np.genfromtxt(infile, delimiter=None, dtype=float)
    X = data[:, :-1]
    t = data[:, [-1]]
    return X, t

#----------------------------------------------------------------------#
#  function: train                                                     #
#----------------------------------------------------------------------#
# Here no. of features M = 3 (floor, bedrooms, age)
# Implement normal equations to compute w = [w0, w1, ..., w_M].
def train(X1, t):
    """Train the data and return the weights w.

    Args:

      X1 (array): Design matrix of size (m+1, n). I.e. There are
        m features and one bias column in the matrix X1.

      t (column): target column vector

    .. note::

       Here the design matrix X1 should have one extra bias term.

    .. warning::

       The operator @ requires python >= 3.5

    """
    # Method 1
    w = np.linalg.inv(X1.T.dot(X1)) .dot(X1.T) .dot(t)
    w = np.array(w).reshape(1, len(w)) # make 1d row array

    # Method 2
    # w = (inv(X1.T @ X1)) @ X1.T @ t
    # w = np.array(w).reshape(1, len(w)) # make 1d row array

    # Method 3
    # w = np.linalg.lstsq(X1,t)[0]
    # w = np.array(w).reshape(1, len(w)) # make 1d row array

    return w

# Compute RMSE on dataset (X, t).
def compute_rmse(X, t, w):
    """Compute the RMSE.

    RMSE is the root mean square error.

    .. math:: RMSE = \sqrt{\sum_{i=1}^{n}  \\frac{(h - t)^2}{n} }

    h is the hypothesis.

    :math:`h = X w^T`

    To find the norm of the residual matrix h-t we may use
    the code::

      # inner1d is the fastest subroutine.
      from numpy.core.umath_tests import inner1d
      np.sqrt(inner1d(h-t,h-t))

      # We can also use another method:
      ht_norm = np.linalg.norm(h - t)

    """

    # Method 1
    h = np.dot(X, w.T) # h = X @ w.T
    rmse = np.sqrt(((h - t) ** 2).mean())

    # Method 2
    # h = np.dot(X, w.T)
    # ht_norm = np.sqrt(inner1d(h-t,h-t))
    # rmse = ht_norm / np.sqrt(len(X))
    # rmse = rmse[0]

    # Method 3
    # norm is square root of sum of squares
    # rmse is norm/ sqrt(n)
    #
    # h = np.dot(X, w.T)
    # ht_norm = np.linalg.norm(h - t)
    # rmse = ht_norm / np.sqrt(len(X))

    # Checking
    # print("t.shape = ", t.shape)
    # print("w.shape = ", w.shape)
    # print("h.shape = ", h.shape)
    # print("X.shape = ", X.shape)
    # print("len(X1) = ", len(X))

    # Checking
    # rmse = 0.0
    # try:
    #     from sklearn.metrics import mean_squared_error
    #     rmse = mean_squared_error(h, t)**0.5
    #     rmse = np.sqrt(np.square(h - t).mean())
    # except:
    #     print('Error: The library sklearn not installed!')

    # Return RMSE
    return rmse

# Compute objective function (cost) on dataset (X, t).
def compute_cost(X, t, w):
    """Compute the cost function.

    .. math:: J = \\frac{1}{2n} \sum_{i=1}^{n}  \\frac{(h - t)^2}{n}

    """

    # Compute cost
    # N = float(len(t))
    # h = np.dot(X, w.T)   # h = X @ w.T
    # J = np.sum((h - t) ** 2) /2 / N

    # One liner
    J = np.sum((X @ w.T - t) ** 2) /2 / float(len(t))

    return J

def check_results(y_train, x1_train):
    """Multivariate Regression with statsmodels.api

    Args:
      y_train (float): target column vector of floats.
      x1_train (array): features+1 dimensional numpy array

    This fits the multivariate linear regression in four lines::

        import statsmodels.api as sm
        model = sm.OLS(y_train, x1_train)
        result = model.fit()
        print (result.summary())

    """
    try:
        import statsmodels.api as sm
        model = sm.OLS(y_train, x1_train)
        result = model.fit()
        print (result.summary())

    except:
        print('Error: statsmodels libray not found!')

##=======================================================================
## Main Program
##=======================================================================
def main():
    parser = argparse.ArgumentParser('Multivariate Exercise.')
    parser.add_argument('-i', '--input_data_dir',
                        type=str,
                        default='../data/multivariate',
                        help='Directory for the multivariate houses dataset.')
    FLAGS, unparsed = parser.parse_known_args()

    # Read the training and test data.
    Xtrain, ttrain = read_data(FLAGS.input_data_dir + "/train.txt")
    Xtest, ttest = read_data(FLAGS.input_data_dir + "/test.txt")

    #  Append ones to the first column
    X1train  = np.append(np.ones_like(ttrain), Xtrain, axis= 1)
    X1test  = np.append(np.ones_like(ttest), Xtest, axis= 1)

    # debug
    # print(" First column X1train[:, [0]] = \n{}".format(X1train[:, [0]]))
    # print(" First row X1train[0] = \n{}".format(X1train[0]))

    # Train model on training examples.
    w = train(X1train, ttrain)

    # Print model parameters.
    print("#"*50)
    print("Multivariate Regression")
    print('Params Mulitvariate: ', w[0], '\n')

    # Print cost and RMSE on training data.
    # train
    E_rms_train_multi = compute_rmse(X1train, ttrain, w)
    J_train_multi = compute_cost(X1train, ttrain, w)

    # test
    E_rms_test_multi = compute_rmse(X1test, ttest, w)
    J_test_multi = compute_cost(X1test, ttest,w)

    print('E_rms_train Multivariate: %0.2e' % E_rms_train_multi)
    print('J_train Multivariate: %0.2e' % J_train_multi)

    # Print cost and RMSE on test data.
    print("\n")
    print('E_rms_test Multivariate: %0.2e' % E_rms_test_multi)
    print('J_test Multivariate: %0.2e' % J_test_multi)

    #===========================================================
    print("\n")
    print("="*50)
    print("Comparison of Univariate and Multivariate")
    fh_train_uni = '../data/univariate/train.txt'
    fh_test_uni = '../data/univariate/test.txt'

    E_rms_train_uni, J_train_uni, E_rms_test_uni, J_test_uni = univariate_reg(fh_train_uni, fh_test_uni)

    print('Univariate             Multivariate')
    print("E_train = {:.4e}     {:.4e}".format(E_rms_train_uni, E_rms_train_multi))
    print("E_test  = {:.4e}     {:.4e}".format(E_rms_test_uni , E_rms_test_multi))
    print("J_train = {:.4e}     {:.4e}".format(J_train_uni    , J_train_multi))
    print("J_test  = {:.4e}     {:.4e}".format(J_test_uni     , J_test_multi))
    print("-"*50)
    print('Multivariate Params are given below:')
    print([ "{:.2e}".format(x) for x in list(w[0])])
    print("#"*20, "The End", "#"*20)
    print("\n")

    # Check result with statsmodels
    # check_results(ttrain, X1train)

if __name__ == "__main__":
    # Run main function
    main()

import numpy,from numpy,import statsmodels


import numpy as np
import matplotlib.pyplot as plt

def condition_points(points):
    """ Conditioning: Normalization of coordinates for numeric stability
    by substracting the mean and dividing by half of the component-wise
    maximum absolute value.
    Args:
        points: (l, 3) numpy array containing unnormalized homogeneous coordinates.

    Returns:
        ps: (l, 3) numpy array containing normalized points in homogeneous coordinates.
        T: (3, 3) numpy array, transformation matrix for conditioning
    """
    t = np.mean(points, axis=0)[:-1]
    s = 0.5 * np.max(np.abs(points), axis=0)[:-1]
    T = np.eye(3)
    T[0:2, 2] = -t
    T[0:2, 0:3] = T[0:2, 0:3] / np.expand_dims(s, axis=1)
    ps = points @ T.T
    return ps, T

def enforce_rank2(A):
    """ Enforces rank 2 to a given 3 x 3 matrix by setting the smallest
    eigenvalue to zero.
    Args:
        A: (3, 3) numpy array, input matrix

    Returns:
        A_hat: (3, 3) numpy array, matrix with rank at most 2
    """

    #
    # You code here
    #
    u, s, vh = np.linalg.svd(A, full_matrices=True)
    s_rank2 = s
    s_rank2[-1] = 0
    A_hat = u @ np.diag(s_rank2) @ vh
    return A_hat

def compute_fundamental(p1, p2):
    """ Computes the fundamental matrix from conditioned coordinates.
    Args:
        p1: (n, 3) numpy array containing the conditioned coordinates in the left image
        p2: (n, 3) numpy array containing the conditioned coordines in the right image

    Returns:
        F: (3, 3) numpy array, fundamental matrix
    """

    #
    # You code here
    #
    A_list = []
    for i, p in enumerate(p1):  # [0:8,:] pick 8 ponts
        temp = np.array(
            [p[0] * p2[i][0], p[1] * p2[i][0], p2[i][0], p[0] * p2[i][1], p[1] * p2[i][1], p2[i][1], p[0], p[1], 1])
        A_list.append(temp)
    A = np.asarray(A_list)
    u, s, vh = np.linalg.svd(A, full_matrices=True)
    V = vh.T
    F = np.reshape(V[:, -1], (3, 3))  # check!
    # F/=F[2,2]
    return F

def eight_point(p1, p2):
    """ Computes the fundamental matrix from unconditioned coordinates.
    Conditions coordinates first.
    Args:
        p1: (n, 3) numpy array containing the unconditioned homogeneous coordinates in the left image
        p2: (n, 3) numpy array containing the unconditioned homogeneous coordinates in the right image

    Returns:
        F: (3, 3) numpy array, fundamental matrix with respect to the unconditioned coordinates
    """

    #
    # You code here
    #
    ps1, T1 = condition_points(p1)
    ps2, T2 = condition_points(p2)
    F_rank3 = compute_fundamental(ps1, ps2)
    F_hat = enforce_rank2(F_rank3)
    F = T2.T @ F_hat @ T1
    return F

def cart2hom(p_c):
    p_h = np.concatenate([p_c, np.ones((p_c.shape[0], 1))], axis=1)
    return p_h

def draw_epipolars(F, p1, img):
    """ Computes the coordinates of the n epipolar lines (X1, Y1) on the left image border and (X2, Y2)
    on the right image border.
    Args:
        F: (3, 3) numpy array, fundamental matrix
        p1: (n, 2) numpy array, cartesian coordinates of the point correspondences in the image
        img: (H, W, 3) numpy array, image data

    Returns:
        X1, X2, Y1, Y2: (n, ) numpy arrays containing the coordinates of the n epipolar lines
            at the image borders
    """

    #
    # You code here
    #
    p1_homo = cart2hom(p1)
    normals = (F @ p1_homo.T).T  # normals: (n,3)
    # only check pixel coordinates on borders
    X1 = []
    X2 = []
    Y1 = []
    Y2 = []  # 2 intersected points on borders
    img_H = img.shape[0]
    img_W = img.shape[1]

    # construct coordinates of pixels on borders
    p_coordinates_borders = []
    for row in range(0, img_H):
        for col in range(0, img_W):
            if (0 < row < img_H - 1 and 0 < col < img_W - 1):
                continue
            p_coordinates_borders.append(np.array([col, row, 1]))  # (x:width, y:height, 1)homogenous
    A_p_coordinates_borders = np.asarray(p_coordinates_borders)

    # dot product of pixel coordinates and normal vector
    dot_product_normals = []  # => (n, number of pixels)
    for normal in normals:  # n iterations
        dot_product_normal = []  # => (number of pixels, )
        for p in p_coordinates_borders:
            normal_vector = normal.flatten()
            dot_product_normal.append(np.dot(normal_vector, p))
        dot_product_normals.append(np.asarray(dot_product_normal))

    A_dot_product_normals = np.asarray(dot_product_normals)
    ind = np.argsort(np.abs(A_dot_product_normals),
                     axis=1)  # (n, number of pixels), sorts along second axis (--), dot_product can be negativ
    # min=np.min(np.abs(A_dot_product_normals), axis=1)
    # print(np.sort(np.abs(A_dot_product_normals[0])))
    # find coordinates of 2 pixels with smallest dot product
    for n, dot_product_ind in enumerate(
            ind):  # row, dot_product_ind[[0,1]] are the indices of the 2 2 pixels with smallest dot product
        ind1 = dot_product_ind[0]
        coor1 = p_coordinates_borders[ind1]
        X1.append(coor1[0])  # W
        Y1.append(coor1[1])  # H
        # print(A_dot_product_normals[n][ind1])

        ind2 = dot_product_ind[1]
        coor2 = p_coordinates_borders[ind2]
        X2.append(coor2[0])
        Y2.append(coor2[1])
        # print(A_dot_product_normals[n][ind2])

    A_X1 = np.asarray(X1)
    A_X2 = np.asarray(X2)
    A_Y1 = np.asarray(Y1)
    A_Y2 = np.asarray(Y2)
    return A_X1, A_X2, A_Y1, A_Y2

def compute_residuals(p1, p2, F):
    """
    Computes the maximum and average absolute residual value of the epipolar constraint equation.
    Args:
        p1: (n, 3) numpy array containing the homogeneous correspondence coordinates from image 1
        p2: (n, 3) numpy array containing the homogeneous correspondence coordinates from image 2
        F:  (3, 3) numpy array, fundamental matrix

    Returns:
        max_residual: maximum absolute residual value
        avg_residual: average absolute residual value
    """

    #
    # You code here
    #
    residual = p1 @ F @ p2.T
    residual_abs = np.abs(residual)
    max_residual = np.max(residual_abs)
    avg_residual = np.mean(residual_abs)
    return max_residual, avg_residual

def compute_epipoles(F):
    """ Computes the cartesian coordinates of the epipoles e1 and e2 in image 1 and 2 respectively.
    Args:
        F: (3, 3) numpy array, fundamental matrix

    Returns:
        e1: (2, ) numpy array, cartesian coordinates of the epipole in image 1
        e2: (2, ) numpy array, cartesian coordinates of the epipole in image 2
    """

    #
    # You code here
    #

    u, s, vh1 = np.linalg.svd(F.T)
    e1 = vh1.T[:, -1]
    e1 /= e1[2]

    u, s, vh2 = np.linalg.svd(F)
    e2 = vh2.T[:, -1]
    e2 /= e2[2]
    return e1[0:2], e2[0:2]


import numpy as np
import scipy.linalg as splg
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.figure as fg

def heatEquationMatrix(n):
    ''' Returns a tridiagonal n^2*n^2 matrix A to solve the heat equation
    in a discrete way by solving the linear system Ax = b, where b is a n^2
    vector such as b[n*x+y] is the heat flux density in the point (x,y)
    and h is the distance on both axis between 2 consecutive points'''
    N = n*n

    A = np.diag([1]*(N-n), -n) + np.diag([1]*(N-n), n)
    sub = -4*np.eye(n) + np.diag([1]*(n-1), 1) + np.diag([1]*(n-1), -1)
    for i in range(0, n):
        A[i*n:(i+1)*n,i*n:(i+1)*n] = sub[:,:]
    return A

def matToVect(M):
    ''' M shoud be a square matrix of size nxn
    Returns a vector containing the values in M, readfrom the left hand column
    to the right hand column, from top to bottom, and stop from top to bottom'''
    n = M.shape[0]
    return M.transpose().reshape((n*n,))

def vectToMat(v):
    ''' v shoud be a vector of length nxn
    Returns a matrix containing the values in v, read from top to bottom and then
    stored from the left hand column to the right hand column, from top to bottom'''
    n = np.sqrt(v.shape[0])
    return v.reshape((n,n))

def solveHeatEquation(heatFlux, h, conductivity):
    n = heatFlux.shape[0]
    heatFlux = matToVect(heatFlux)*h*h/conductivity
    M = heatEquationMatrix(n)
    T = np.linalg.cholesky(-M)
    y = splg.solve_triangular(T, heatFlux, lower=True, check_finite=False)
    x = splg.solve_triangular(-T.transpose(), y, check_finite=False)
    return vectToMat(x)

def printHeatSolution(sol):
    plt.imshow(sol.transpose(), interpolation='bilinear', cmap=cm.jet_r)
    plt.colorbar()
    plt.show()

# size = 50
# h = 0.01
# airConductivity = 0.025
# heatFlux = np.zeros((size,size))
# # ext[size/2][size/2] = 200
# heatFlux[10][10] = 100
# heatFlux[10][30] = 150
# heatFlux[30][10] = 200
# heatFlux[30][30] = 250

# printHeatSolution(solveHeatEquation(heatFlux, h, airConductivity))


import numpy as np

def forward_substitution(L, b):
    y = np.full_like(b, 0)
    for k in range(len(b)):
        y[k] = b[k]
        for i in range(k):
            y[k] = y[k] - (L[k, i] * y[i])
        y[k] = y[k] / L[k, k]
    return y

def backward_substitution(U, y):
    x = np.full_like(y, 0)
    for k in range(len(x), 0, -1):
        x[k - 1] = (y[k - 1] - np.dot(U[k - 1, k:], x[k:])) / U[k - 1, k - 1]
    return x

def cholesky_decomposition(A):
    n = len(A)
    L = np.ones((n, n)) * 0.0
    for i in range(n):
        for j in range(i + 1):
            if i == j:
                sum = 0
                for k in range(i):
                    sum = sum + L[i][k] ** 2
                L[i][i] = (A[i][i] - sum) ** 0.5
            else:
                sum = 0
                for k in range(j):
                    sum = sum + L[i][k] * L[j][k]
                L[i][j] = (A[i][j] - sum) / L[j][j]
    U = L.transpose()

    return L, U

def solution(A, b):

    y = forward_substitution(L, b)
    x = backward_substitution(U, y)

    return x

A = np.array([[1, 1, 1], [1, 2, 2], [1, 2, 3]])
print('A:', A)
b = np.array([1, 1.5, 3])
print('b:', b)
L, U = cholesky_decomposition(A)
print('L:', L)
print('U:', U)

print('***SOLUTION***')
x = solution(A, b)
print('x:', x)


import sys
sys.path.append("../")
from sir import *
from sir.SIR_continuous_reinfected import *
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt
t=pd.read_csv("../data/case_daily_trends__united_states.csv",header=2).iloc[:,2] # read in daily case in US
t=pd.read_csv("../data/case_total_and_rate_per_100000__united_states.csv",header=2).iloc[:,1] # read in total case in US
total_case=np.array(t)
total_case[:10]

totalpopulation=328200000 # total population in us
S=totalpopulation-total_case
S=S/totalpopulation # in percentage
IandR=total_case/totalpopulation
time=[*range(len(total_case), 0, -1)]
plt.plot(time,S,label="S")
plt.plot(time,IandR,label="I+R")
plt.legend()
plt.xlabel("number of days")
plt.ylabel("probabillity")
plt.show()

b=0.1
k=0.1
time=319
ii=0.01
r=0
q=1

sol1=SIR_continuous_reinfected(b,k,time,ii,r)
plt.plot(sol1.t, sol1.y[0], c='b',label='s')    # generate the plot
plt.plot(sol1.t, (sol1.y[1]+sol1.y[2]), c='g',label='r+i')
#plt.plot(sol1.t, sol1.y[2]*1000, c='r',label='i')
plt.show()

def loss(b,k,r):
    """
    loss function with parameter b, k,r
    """
    ii=0.01
    time=len(S)
    q=1
    sol1=SIR_continuous_reinfected(b,k,time,ii,r)
    return np.linalg.norm(sol1.y[0]-S)+np.linalg.norm((sol1.y[1]+sol1.y[2])-IandR)

def loss2(x):
    """
    loss function with vector x that contains b,k,r
    """
    b=x[0]
    k=x[1]
    r=x[2]
    ii=0.01
    time=len(S)
    q=1
    sol1=SIR_continuous_reinfected(b,k,time,ii,r)
    return np.linalg.norm(sol1.y[0]-S)+np.linalg.norm((sol1.y[1]+sol1.y[2])-IandR)

def loss3(x):
    """
    loss function for r
    """
    b=0.0001
    k=0.074
    ii=0.01
    time=len(S)
    q=1
    sol1=SIR_continuous_reinfected(b,k,time,ii,x)
    return np.linalg.norm(sol1.y[0]-S)+np.linalg.norm((sol1.y[1]+sol1.y[2])-IandR)

sol = opt.minimize(loss2, np.array([-9.66889007e-06, -9.85879953e-01,  9.77224552e-01]))
sol

losslist=[]
rlist=[]
n=1000
for i in range(n):
    r=i/n
    losslist.append(loss3(r))
    rlist.append(r)
plt.plot(rlist,losslist)
plt.xlabel('r')
plt.ylabel('loss')
plt.title('The relationship of reinfection rate and loss function')
plt.savefig('rvsloss')

b=0.0001
k=0.074
time=319
ii=0.01
r=0.01
q=1
times=[*range(len(total_case), 0, -1)]
sol1=SIR_continuous_reinfected(b,k,time,ii,r)
plt.plot(sol1.t, sol1.y[0], c='b',label='S(simulation)')    # generate the plot
plt.plot(sol1.t, (sol1.y[1]+sol1.y[2]), c='g',label='R+I(Simulation)')
plt.plot(times,S,label="S(data)")
plt.plot(times,IandR,label="I+R(data)")
plt.legend()
plt.title("COVID 19: SIR model vs data")
plt.savefig('modelvsdata')


#!/usr/bin/env python
# coding: utf-8
## Monte Carlo - Forecasting StockType Prices - Part I
# *Suggested Answers follow (usually there are multiple ways to solve a problem in Python).*
# Download the data for Microsoft (�MSFT�) from Yahoo Finance for the period �2000-1-1� until today.
# Download the data for Microsoft (�MSFT�) from IEX for the period �2015-1-1� until today.
# Forecasting Future StockType Prices � continued:
import numpy as np
import pandas as pd
from pandas_datareader import data as wb
import matplotlib.pyplot as plt
from scipy.stats import norm

# get_ipython().run_line_magic('matplotlib', 'inline')
# In[2]:
# ticker = 'PG'
# ticker = 'MSFT'
# data = pd.DataFrame()
# data[ticker] = wb.DataReader(ticker, data_source='yahoo', start='2007-1-1')['Adj Close']
# data[ticker] = wb.DataReader(ticker, data_source='iex', start='2015-1-1')['close']
data = pd.read_csv('Section-17_PG_2007_2017.csv', index_col='Date')
data = pd.read_csv('Section-17_MSFT_2000.csv', index_col='Date')
data.plot(figsize=(10, 6));
# Use the .pct_change() method to obtain the log returns of Microsoft for the designated period.
log_returns = np.log(1 + data.pct_change())
log_returns.tail()
log_returns.plot(figsize=(10, 6))
# Assign the mean value of the log returns to a variable, called �U�, and their variance to a variable, called �var�.
u = log_returns.mean()
u
# In[7]:
var = log_returns.var()
var
# Calculate the drift, using the following formula:
# $$
# drift = u - \frac{1}{2} \cdot var
# $$
drift = u - (0.5 * var)
drift
# Store the standard deviation of the log returns in a variable, called �stdev�.
stdev = log_returns.std()
stdev
# ******
# Use �.values� to transform the *drift* and the *stdev* objects into arrays.
type(drift)
drift.values
# In[4]:
type(stdev)
# In[12]:
np.array(drift)
drift.values
# In[14]:
stdev.values
# Forecast future stock prices for every trading day a year ahead. So, assign 250 to �t_intervals�.
# Let�s examine 10 possible outcomes. Bind �iterations� to the value of 10.
t_intervals = 250
t_intervals = 1000
iterations = 10
# Use the formula we have provided and calculate daily returns.
# $$
# r = drift + stdev \cdot z
# $$
# $$
# daily\_returns = exp({drift} + {stdev} * z),
# $$
# $$
# where\  z = norm.ppf(np.random.rand(t\_intervals, iterations)
# $$
# $$
# daily\_returns = e^{r}
# $$
daily_returns = np.exp(drift.values + stdev.values * norm.ppf(np.random.rand(t_intervals, iterations)))
daily_returns
# $$
# S_t = S_0 \mathbin{\cdot} daily\_return_t
# $$
# $$
# S_{t+1} = S_t \mathbin{\cdot} daily\_return_{t+1}
# $$
# $$...$$
# $$
# S_{t+999} = S_{t+998} \mathbin{\cdot} daily\_return_{t+999}
# $$
# ***
# Create a variable S0 equal to the last adjusted closing price of Microsoft. Use the �iloc� method.
S0 = data.iloc[-1]
S0
# Create a variable price_list with the same dimension as the daily_returns matrix.
price_list = np.zeros_like(daily_returns)
price_list
price_list[0]
# Set the values on the first row of the price_list array equal to S0.
price_list[0] = S0
price_list
# Create a loop in the range (1, t_intervals) that reassigns to the price in time t the product of the price in day (t-1) with the value of the daily returns in t.
for t in range(1, t_intervals):
    price_list[t] = price_list[t - 1] * daily_returns[t]
price_list
# Finally, plot the obtained price list data.
plt.figure(figsize=(10, 6))
plt.plot(price_list);


import numpy as np

def power_eig(A, b0=None, max_steps=50, eps=1e-10):
    # set initial vector
    if b0 is None:
        b = np.random.rand(A.shape[0])
    else:
        b = b0
    # apply power method until we took too many steps or we converged
    for i in range(max_steps):
        new_b = A.dot(b)
        new_b /= np.linalg.norm(new_b)
        if np.linalg.norm(b - new_b) < eps:
            b = new_b # eigen vector
            break
        b = new_b
    else:
        raise ValueError("failed to converge")
    # compute eigen value
    # Ab = lb
    lb = A.dot(b)
    b_has_zero = b != 0
    eigen_val = np.mean(b[b_has_zero]/b[b_has_zero])
    return eigen_val, b

A = np.array([[0, 0, 1],
              [0, 1, 0],
              [1, 0, 0]], dtype="float")

val, vec = power_eig(A, b0=np.array([1,1,1]), eps=1e-5)

print("matrix:\n", A)
print("has eigen value", val, "with eigenvector", vec)


# -*- coding: utf-8 -*-
"""

@author: Sushant

"""

import numpy as np
import scipy

######################################## Least Square Linear Regression ####################################
def LinearRegression(data,labels):
    numdata = int( np.size(data,0) )
    b1 = np.hstack(( data,np.ones((numdata,1)) ) )
    XXT = np.matmul(b1.T,b1)
    invXXT = np.linalg.pinv(XXT)
    b2 = np.matmul(b1.T,labels)
    w = np.matmul(invXXT,b2)
    return w

###################################### Ridges Regression ###################################################

def RidgeRegression(data,labels,lambda):
    numdata = int( np.size(data,0) )
    b1 = np.hstack(( data,np.ones((numdata,1)) ) )
    XXT = np.matmul(b1.T,b1) + lambda*np.identity(np.size(b1,1))
    b2 = np.matmul(b1.T,labels)
    #solved using Cholesky decompostion Ax = b
    b3 = scipy.linalg.cho_factor( XXT )
    w = scipy.linalg.cho_solve(b3,b2)
    return w

###################################### Predict values and Least Square Error###############################
def PredictLabels(testdata,w):
    numdata = int( np.size(testdata,0) )
    b3 = np.hstack( ( testdata,np.ones((numdata,1)) ) )
    pred = np.matmul(b3,w)
    return pred

def ltsqerror(prelabels,actlabels):
    return np.sum((prelabels-actlabels)**2)/int(np.size(prelabels,0))

####################################### Kernel Ridges Regression ########################################

def linear(x1,x2,p = None):
    return np.dot(x1,x2)

def polynomial(x1,x2,d):
    return ( 1+np.dot(x1,x2) )**d

def rbf(x1,x2,l):
    return  np.exp( -np.divide(np.dot(x1-x2,x1-x2), 2*(l**2 ) )    )

def KernelRidgeRegression(data,labels,lamda,kernel,p):
    numdata =  int( np.size(data,0) )
    traindata = np.asarray(data)
    #=========Kernel matrix======================
    K =  np.zeros((numdata,numdata))
    for i in range(0,numdata):
        for j in range(0,numdata):
            K[i,j] = kernel(traindata[i,:],traindata[j,:],p)
    #solved using Cholesky decompostion Ax = b
    b1 = scipy.linalg.cho_factor( K + lamda*np.identity(numdata) )
    alphas = scipy.linalg.cho_solve(b1,labels)
    return alphas

def KernelRidgesRegression_predict(traindata1,alphas,testdata1,kernel,p):
    numtraindata =  int( np.size(traindata1,0) )
    numtestdata =  int( np.size(testdata1,0) )
    traindata = np.asarray(traindata1)
    testdata = np.asarray(testdata1)
    predlabels = np.zeros((numtestdata,1))
    K =  np.zeros((numtestdata,numtraindata))
    for j in range(0,numtestdata):
        for i in range(0,numtraindata):
            K[j,i] = kernel(traindata[i,:],testdata[j,:],p)
    predlabels = np.dot(K,alphas)

    return predlabels