import numpy as np
import matplotlib.pyplot as plt

def swap_rows(A, a, b):
    """Rows two rows in a matrix, switch row a with row b

    args:
    A: matrix to perform row swaps on
    a: row index of matrix
    b: row index of matrix

    returns: nothing

    side effects:
    changes A to rows a and b swapped
    """
    assert (a>=0) and (b>=0)
    N = A.shape[0] #number of rows
    assert (a<N) and (b<N) #less than because 0-based indexing
    temp = A[a,:].copy()
    A[a,:] = A[b,:].copy()
    A[b,:] = temp.copy()

def LU_factor(A,LOUD=True):
    """Factor in place A in L*U=A. The lower triangular parts of A
    are the L matrix.  The L has implied ones on the diagonal.

    Args:
        A: N by N array
    Returns:
        a vector holding the order of the rows, relative to the original order
    Side Effects:
        A is factored in place.
    """
    [Nrow, Ncol] = A.shape
    assert Nrow == Ncol
    N = Nrow
    #create scale factors
    s = np.zeros(N)
    count = 0
    row_order = np.arange(N)
    for row in A:
        s[count] = np.max(np.fabs(row))
        count += 1
    if LOUD:
        print("s =",s)
    if LOUD:
        print("Original Matrix is\n",A)
    for column in range(0,N):
        #swap rows if needed
        largest_pos = np.argmax(np.fabs(A[column:N,column]/s[column])) + column
        if (largest_pos != column):
            if (LOUD):
                print("Swapping row",column,"with row",largest_pos)
                print("Pre swap\n",A)
            swap_rows(A,column,largest_pos)
            #keep track of changes to RHS
            tmp = row_order[column]
            row_order[column] = row_order[largest_pos]
            row_order[largest_pos] = tmp
            #re-order s
            tmp = s[column]
            s[column] = s[largest_pos]
            s[largest_pos] = tmp
            if (LOUD):
                print("A =\n",A)
        for row in range(column+1,N):
            mod_row = A[row]
            factor = mod_row[column]/A[column,column]
            mod_row = mod_row - factor*A[column,:]
            #put the factor in the correct place in the modified row
            mod_row[column] = factor
            #only take the part of the modified row we need
            mod_row = mod_row[column:N]
            A[row,column:N] = mod_row
    return row_order
def LU_solve(A,b,row_order):
    """Take a LU factorized matrix and solve it for RHS b

    Args:
        A: N by N array that has been LU factored with
        assumed 1's on the diagonal of the L matrix
        b: N by 1 array of righthand side
        row_order:  list giving the re-ordered equations
        from the the LU factorization with pivoting
    Returns:
        x: N by 1 array of solutions
    """
    [Nrow, Ncol] = A.shape
    assert Nrow == Ncol
    assert b.size == Ncol
    assert row_order.max() == Ncol-1
    N = Nrow

    #reorder the equations
    tmp = b.copy()
    for row in range(N):
        b[row_order[row]] = tmp[row]

    x = np.zeros(N)
    #temporary vector for L^-1 b
    y = np.zeros(N)
    #forward solve
    for row in range(N):
        RHS = b[row]
        for column in range(0,row):
            RHS -= y[column]*A[row,column]
        y[row] = RHS
    #back solve
    for row in range(N-1,-1,-1):
        RHS = y[row]
        for column in range(row+1,N):
            RHS -= x[column]*A[row,column]
        x[row] = RHS/A[row,row]
    return x

def inversePowerBlock(M11, M21, M22, P11, P12,epsilon=1.0e-6,LOUD=False):
    """Solve the generalized eigenvalue problem
    (M11  0 ) (phi_1) = 1/k (P11 P12) using inverse power iteration
    (M21 M22) (phi_2)       (0    0 )
    Inputs
    Mij: An LHS matrix (must be invertible)
    P1j: A fission matrix
    epsilon: tolerance on eigenvalue
    Outputs:
    l: the smallest eigenvalue of the problem
    x1: the associated eigenvector for the first block
    x2: the associated eigenvector for the second block
    """
    N,M = M11.shape
    assert(N==M)
    #generate initial guess
    x1 = np.random.random((N))
    x2 = np.random.random((N))
    l_old = np.linalg.norm(np.concatenate((x1,x2)))
    x1 = x1/l_old
    x2 = x2/l_old
    converged = 0
    #compute LU factorization of M11
    row_order11 = LU_factor(M11,LOUD=False)
    #compute LU factorization of M22
    row_order22 = LU_factor(M22,LOUD=False)
    iteration = 1;
    while not(converged):
        #solve for b1
        b1 = LU_solve(M11,np.dot(P11,x1) + np.dot(P12,x2),row_order11)
        #solve for b2
        b2 = LU_solve(M22,np.dot(-M21,b1),row_order22)
        #eigenvalue estimate is norm of combined vectors
        l = np.linalg.norm(np.concatenate((b1,b2)))
        x1 = b1/l
        x2 = b2/l
        converged = (np.fabs(l-l_old) < epsilon)
        l_old = l
        if (LOUD):
            print("Iteration:",iteration,"\tMagnitude of l =",1.0/l)
        iteration += 1
    return 1.0/l, x1, x2

def create_grid(R,I):
    """Create the cell edges and centers for a
    domain of size R and I cells
    Args:
        R: size of domain
        I: number of cells

    Returns:
        Delta_r: the width of each cell
        edges: the cell edges of the grid
        centers: the cell centers of the grid
    """
    Delta_r = float(R)/I
    centers = np.arange(I)*Delta_r + 0.5*Delta_r
    edges = np.arange(I+1)*Delta_r
    return Delta_r, centers, edges
def TwoGroupDiffusionEigenvalue(R,I,D1,D2,Sig_r1,Sig_r2, nu_Sigf1, nu_Sigf2,Sig_s12, geometry,epsilon = 1.0e-8):
    """Solve a neutron diffusion eigenvalue problem in a 1-D geometry
    using cell-averaged unknowns
    Args:
        R: size of domain
        I: number of cells
        Dg: name of function that returns diffusion coefficient for a given r
        Sig_rg: name of function that returns Sigma_rg for a given r
        nuSig_fg: name of function that returns nu Sigma_fg for a given r
        Sig_s12: name of function that returns Sigma_s12 for a given r
        geometry: shape of problem
                0 for slab
                1 for cylindrical
                2 for spherical

    Returns:
        k: the multiplication factor of the system
        phi_fast:  the fast flux fundamental mode with norm 1
        phi_thermal:  the thermal flux fundamental mode with norm 1
        centers: position at cell centers

    """
    #create the grid
    Delta_r, centers, edges = create_grid(R,I)
    M11 = np.zeros((I+1,I+1))
    M21 = np.zeros((I+1,I+1))
    M22 = np.zeros((I+1,I+1))
    P11 = np.zeros((I+1,I+1))
    P12 = np.zeros((I+1,I+1))
    #define surface areas and volumes
    assert( (geometry==0) or (geometry == 1) or (geometry == 2))
    if (geometry == 0):
        #in slab it's 1 everywhere except at the left edge
        S = 0.0*edges+1
        S[0] = 0.0 #to enforce Refl BC
        #in slab its dr
        V = 0.0*centers + Delta_r
    elif (geometry == 1):
        #in cylinder it is 2 pi r
        S = 2.0*np.pi*edges
        #in cylinder its pi (r^2 - r^2)
        V = np.pi*( edges[1:(I+1)]**2
                   - edges[0:I]**2 )
    elif (geometry == 2):
        #in sphere it is 4 pi r^2
        S = 4.0*np.pi*edges**2
        #in sphere its 4/3 pi (r^3 - r^3)
        V = 4.0/3.0*np.pi*( edges[1:(I+1)]**3
                   - edges[0:I]**3 )

    #Set up BC at R
    M11[I,I] = 1.0
    M11[I,I-1] = 1.0
    M22[I,I] = 1.0
    M22[I,I-1] = 1.0

    #fill in rest of matrix
    for i in range(I):
        r = centers[i]
        M11[i,i] = (0.5/(Delta_r * V[i])*((D1(r)+D1(r+Delta_r))*S[i+1]) +
                  Sig_r1(r))
        M22[i,i] = (0.5/(Delta_r * V[i])*((D2(r)+D2(r+Delta_r))*S[i+1]) +
                  Sig_r2(r))
        M21[i,i] = -Sig_s12(r)
        P11[i,i] = nu_Sigf1(r)
        P12[i,i] = nu_Sigf2(r)
        if (i>0):
            M11[i,i-1] = -0.5*(D1(r)+D1(r-Delta_r))/(Delta_r * V[i])*S[i]
            M11[i,i] += 0.5/(Delta_r * V[i])*((D1(r)+D1(r-Delta_r))*S[i])
            M22[i,i-1] = -0.5*(D2(r)+D2(r-Delta_r))/(Delta_r * V[i])*S[i]
            M22[i,i] += 0.5/(Delta_r * V[i])*((D2(r)+D2(r-Delta_r))*S[i])
        M11[i,i+1] = -0.5*(D1(r)+D1(r+Delta_r))/(Delta_r * V[i])*S[i+1]
        M22[i,i+1] = -0.5*(D2(r)+D2(r+Delta_r))/(Delta_r * V[i])*S[i+1]

    #find eigenvalue
    l,phi1,phi2 = inversePowerBlock(M11,M21,M22,P11,P12,epsilon)
    k = 1.0/l
    #remove last element of phi because it is outside the domain
    phi1 = phi1[0:I]
    phi2 = phi2[0:I]
    return k, phi1, phi2, centers

def hide_spines(intx=False,inty=False):
    """Hides the top and rightmost axis spines from view for all active
    figures and their respective axes."""

    # Retrieve a list of all current figures.
    figures = [x for x in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
    if (plt.gca().get_legend()):
        plt.setp(plt.gca().get_legend().get_texts(), fontproperties=font)
    for figure in figures:
        # Get all Axis instances related to the figure.
        for ax in figure.canvas.figure.get_axes():
            # Disable spines.
            ax.spines['right'].set_color('none')
            ax.spines['top'].set_color('none')
            # Disable ticks.
            ax.xaxis.set_ticks_position('bottom')
            ax.yaxis.set_ticks_position('left')
           # ax.xaxis.set_major_formatter(mtick.FuncFormatter(lambda v,_: ("10$^{%d}$" % math.log(v,10)) ))
            for label in ax.get_xticklabels() :
                label.set_fontproperties(font)
            for label in ax.get_yticklabels() :
                label.set_fontproperties(font)
            #ax.set_xticklabels(ax.get_xticks(), fontproperties = font)
            ax.set_xlabel(ax.get_xlabel(), fontproperties = font)
            ax.set_ylabel(ax.get_ylabel(), fontproperties = font)
            ax.set_title(ax.get_title(), fontproperties = font)
            if (inty):
                ax.yaxis.set_major_formatter(mtick.FormatStrFormatter('%d'))
            if (intx):
                ax.xaxis.set_major_formatter(mtick.FormatStrFormatter('%d'))
def show(nm,a=0,b=0):
    hide_spines(a,b)
    #ax.xaxis.set_major_formatter(mtick.FuncFormatter(lambda v,_: ("10$^{%d}$" % math.log(v,10)) ))
    #plt.yticks([1,1e-2,1e-4,1e-6,1e-8,1e-10,1e-12], labels)
    #ax.yaxis.set_major_formatter(mtick.FuncFormatter(lambda v,_: ("10$^{%d}$" % math.log(v,10)) ))
    plt.savefig(nm);
    plt.show()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Jiajun Zhu

"""
Statistical Hypothesis Test(significance test)
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import shapiro
from scipy.stats import normaltest
from scipy.stats import anderson
from scipy.stats import chi2_contingency
from scipy.stats import spearmanr
from scipy.stats import pearsonr
from scipy.stats import kendalltau
import pandas as pd

class SignificanceTest:
    """
    If Data Is Gaussian:
	Use Parametric Statistical Methods
    Else:
	Use Nonparametric Statistical Methods

    """

    def __init__(self):
        """
        constructor
        """
        np.random.seed(0)

    def normality_test(self, data, plot_hist=False):
        """
        Tests whether a data sample has a Normal(Gaussian) distribution.

        Assumptions
            Observations in each sample are independent and identically distributed (iid).

        Interpretation
            H0: the sample has a Gaussian distribution.(null hypothesis)
            H1: the sample does not have a Gaussian distribution.

        Note: Null hypothesis usually means there's no relationship between two data sets.

        Return:
            percentage of success: show the percentage of success. (soft_fail)
                if all failed, we call that hard fail.
                if some failed, we call that soft fail.
        """

        if plot_hist is True:
            plt.hist(data)
            plt.show()

        test_succeeded = 0
        num_tests = 0

        # Shapiro-Wilk Test
        # use p-value only as p-value is easier to evaluate.
        stat, p = shapiro(data)
        num_tests += 1
        # interpret
        alpha = 0.05
        if p > alpha:
            # failed to reject null hypothesis
            test_succeeded += 1

        # D’Agostino’s K^2 test
        stat, p = normaltest(data)
        num_tests += 1
        # interpret
        alpha = 0.05
        if p > alpha:
            test_succeeded += 1

        # Anderson-Darling Test
        result = anderson(data)
        num_tests += 1
        p = 0

        anderson_test_succeeded = True
        for i in range(len(result.critical_values)):
            if result.statistic >= result.critical_values[i]:
                anderson_test_succeeded = False
        if anderson_test_succeeded is True:
            test_succeeded += 1

        test_succeeded_pert = test_succeeded/num_tests

        if test_succeeded_pert == 0:
            print("Samples do not follows a normal distribution. (hard fail)")
        elif test_succeeded_pert == 1:
            print("Samples do follows a normal distribution.")
        else:
            print("Samples may not follows a normal distribution. (soft fail)")

        return test_succeeded_pert

    def correlation_test(self, data1, data2, normal_dist=True, corr_algo="spearman"):
        """
        Checking if two samples are related. The following 3 rank correlation are provided.

        1. Pearson’s Correlation Coefficient
            Tests whether two samples have a monotonic relationship.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample are normally distributed.
                Observations in each sample have the same variance.

            Interpretation
                H0: the two samples are independent.
                H1: there is a dependency between the samples.

        2. Spearman’s Rank Correlation
            Tests whether two samples have a monotonic relationship.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample can be ranked.

            Interpretation
                H0: the two samples are independent.
                H1: there is a dependency between the samples.

        3. Kendall’s Rank Correlation
            Tests whether two samples have a monotonic relationship.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample can be ranked.

            Interpretation
                H0: the two samples are independent.
                H1: there is a dependency between the samples.

        Args:
            data1: input data1
            data2: input data2
            normal_dist: if samples have Normal Distribution.
            corr_algo: rank correlation algorithm name.

        Returns:
            correlations
        """
        algo_name_spearman = "spearman"
        algo_name_kendall = "kendall"

        if normal_dist is True:
            corr, p = pearsonr(data1, data2)
        else:
            if corr_algo == algo_name_spearman:
                corr, p = spearmanr(data1, data2)
            elif corr_algo == algo_name_kendall:
                corr, p = kendalltau(data1, data2)
            else:
                raise ValueError("not supported rank correlation!")

        # interpret the significance
        alpha = 0.05
        if p > alpha:
            print('Samples are uncorrelated (fail to reject H0) p=%.3f' % p)
        else:
            print('Samples are correlated (reject H0) p=%.3f' % p)

        return corr

    def chi_square_test(self, dataframe):
        """
        Chi-Squared Test
            Tests whether two categorical variables are related or independent.

            Assumptions
                Observations used in the calculation of the contingency table are independent.
                25 or more examples in each cell of the contingency table.

            Interpretation
                H0: the two samples are independent.
                H1: there is a dependency between the samples.
        """

        #              Survived  Pclass     Sex   Age     Fare Cabin Embarked
        # PassengerId
        # 1                   0       3    male  22.0   7.2500   NaN        S
        # 2                   1       1  female  38.0  71.2833   C85        C
        # 3                   1       3  female  26.0   7.9250   NaN        S
        # 4                   1       1  female  35.0  53.1000  C123        S
        # 5                   0       3    male  35.0   8.0500   NaN        S
        #                                \/
        # Pclass    1    2    3
        # Sex
        # female   94   76  144
        # male    122  108  347
        crossed = pd.crosstab(dataframe.A, dataframe.B)
        stat, p, dof, expected = chi2_contingency(crossed)
        alpha = 0.05
        if p > alpha:
            print('Samples are unrelated (fail to reject H0) p=%.3f' % p)
        else:
            print('Samples are related (reject H0) p=%.3f' % p)

    def compare_data_samples(self, data1, data2, normal_dist=True, test_name="t-test"):
        """
        Compare tow data samples.

        Parametric Statistical Hypothesis Tests
        1. Student’s t-test
            Tests whether the means of two independent samples are significantly different.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample are normally distributed.
                Observations in each sample have the same variance.

            Interpretation
                H0: the means of the samples are equal.
                H1: the means of the samples are unequal.

        2. Paired Student’s t-test
            Tests whether the means of two paired samples are significantly different.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample are normally distributed.
                Observations in each sample have the same variance.
                Observations across each sample are paired.

            Interpretation
                H0: the means of the samples are equal.
                H1: the means of the samples are unequal.

        3. Analysis of Variance Test (ANOVA)
            Tests whether the means of two or more independent samples are significantly different.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample are normally distributed.
                Observations in each sample have the same variance.

            Interpretation
                H0: the means of the samples are equal.
                H1: one or more of the means of the samples are unequal.

        Nonparametric Statistical Hypothesis Tests
        4. Mann-Whitney U Test
            Tests whether the distributions of two independent samples are equal or not.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample can be ranked.

            Interpretation
                H0: the distributions of both samples are equal.
                H1: the distributions of both samples are not equal.

        5. Wilcoxon Signed-Rank Test
            Tests whether the distributions of two paired samples are equal or not.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample can be ranked.
                Observations across each sample are paired.

            Interpretation
                H0: the distributions of both samples are equal.
                H1: the distributions of both samples are not equal.

        6. Kruskal-Wallis H Test
            Tests whether the distributions of two or more independent samples are equal or not.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample can be ranked.

            Interpretation
                H0: the distributions of all samples are equal.
                H1: the distributions of one or more samples are not equal.

        7. Friedman Test
            Tests whether the distributions of two or more paired samples are equal or not.

            Assumptions
                Observations in each sample are independent and identically distributed (iid).
                Observations in each sample can be ranked.
                Observations across each sample are paired.

            Interpretation
                H0: the distributions of all samples are equal.
                H1: the distributions of one or more samples are not equal.
        """
        from scipy.stats import ttest_ind
        stat, p = ttest_ind(data1, data2)

        from scipy.stats import ttest_rel
        stat, p = ttest_rel(data1, data2)

        from scipy.stats import f_oneway
        stat, p = f_oneway(data1, data2, ...)

        from scipy.stats import mannwhitneyu
        stat, p = mannwhitneyu(data1, data2)

        from scipy.stats import wilcoxon
        stat, p = wilcoxon(data1, data2)

        from scipy.stats import kruskal
        stat, p = kruskal(data1, data2, ...)

        from scipy.stats import friedmanchisquare
        stat, p = friedmanchisquare(data1, data2, ...)


# ############################################################################
# math.py
# =======
# Authors : Adrien Besson [adribesson@gmail.com] and Matthieu Simeoni [matthieu.simeoni@gmail.com]
# ############################################################################
"""
Miscellaneous mathematical functions.
"""

import numpy as np

def sign(x: np.ndarray) -> np.ndarray:
    """
    Sign function, supports complex vectors too.
    :param x: np.ndarray
    Vector.
    :return: np.ndarray
    Sign of the vector.

    Note: For complex case see the book "A Mathematical Introduction to Compressive Sensing" pages 72 and 484.
    """
    if np.isreal(x):
        return np.sign(x)
    else:
        y = x / np.abs(x)
        y[np.abs(x) == 0] = 0
        return x

def sinc(t: np.ndarray, M: int, period: float) -> np.ndarray:
    """
    Cardinal sine.
    :param t: np.ndarray
    :param M: int
    :param period: float
    :return: np.ndarray
    """
    val = np.sin((2 * M + 1) * np.pi * t / period) / np.sin(np.pi * t / period) / (2 * M + 1)
    return val


import numpy as np
#from scipy.stats import *

def _switch(base, p='e'):
    return {
        2: np.log2(p),
        'e': np.log(p)

    }[base]

def _entropy(pk, base):
    '''
    if  1 != sum all values that in all pk  ,then should normalize pk,for example,[0.2,0.3],will be 0.2/(0.2+0.3),
    0.3/(0.2+0.3)
    there is many ways to find a solution ,like H =- log(p^p)
    :param pk: probability array_like ,each from 0 to 1
    :return: entropy

    '''
    entropy_sum = 0
    pk = np.array(pk)
    pk = pk/np.sum(pk,axis=0)

    #power = 1
    for p in pk:
        entropy_sum += -p * (_switch(base, p))
        #power *= np.power(p,p)
    #entropy_sum = np.log2(power)
    return entropy_sum

def _cross_entropy(P,Q,base='e'):
    '''
    cross_entropy = H(P)+DKL(P||Q)
    or = plog(q)

    :return:
    '''
    cross_entropy = 0

    for index,p in enumerate(P):
        cross_entropy+=p*_switch(base,Q[index])

    return cross_entropy

def _relative_entropy(P,Q,base='e'):
    '''
    also knowns as the kullbakc-leibler divergence of q from p
    P:array_like
    Q:array_like
    :return:
    '''
    DKL = 0
    for index,p in enumerate(P):
        #print(p*_switch(base,p/(Q[index])))
        DKL+=p*_switch(base,p/(Q[index]))
        #print(DKL)

    return DKL

def _gradient_descent():
    '''
    gradient descent for cross entropy in binary case

    j(tha)  = (h(x) - y )*x
    :return:
    '''

def test():
    print('test in entropy package')
    return

def main():
    pk = [2/5,3/5]
    Q = [i/12 for i in range(1,10)]

    P = [0.333,0.333,0.333]
    Q = [0.36,0.48,0.16]
    #print(entropy(pk,base=2))
    print(_entropy(pk,base=2))
    #print(_relative_entropy(P,Q))

if __name__ == '__main__':
    main()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time     : 2018/11/09 19:02
# @Author   : Iydon
# @File     : 6.1.py

import numpy as np
np.set_printoptions(precision=3)
np.set_printoptions(suppress=True)

def chopping(num, dig):
    eps = 10**-dig / 2
    return np.round(num-eps, dig)

def gaussian_elimination(A, b, num_dig):
    """
    Chopping.
    """
    np_result = A**-1 * b
    print(np_result.T)
    n = A.shape[0]
    x = np.zeros((n,1))
    tmp = 0
    for k in range(n-1):
        for i in range(k+1,n):
            m = A[i,k] / A[k,k]
            A[i,:] = chopping( A[i,:] - m*A[k,:], num_dig )
            b[i,0] = chopping( b[i,0] - m*b[k,0], num_dig )
        print(np.hstack((A,b)))
    x[-1,0] = chopping( b[-1,0] / A[-1,-1], num_dig )
    for i in range(n-2,-1,-1):
        for j in range(i+1,n):
            tmp += chopping( A[i,j] * x[j,0], num_dig )
        x[i,0] = chopping( (b[i,0] - tmp) / A[i,i], num_dig )
        tmp = 0
    return x

def gaussian_elimination(A, b, num_dig):
    """
    Rounding.
    """
    np_result = A**-1 * b
    print(np_result.T)
    n = A.shape[0]
    x = np.zeros((n,1))
    tmp = 0
    for k in range(n-1):
        for i in range(k+1,n):
            m = A[i,k] / A[k,k]
            A[i,:] = np.round( A[i,:] - m*A[k,:], num_dig )
            b[i,0] = np.round( b[i,0] - m*b[k,0], num_dig )
        print(np.hstack((A,b)))
    x[-1,0] = np.round( b[-1,0] / A[-1,-1], num_dig )
    for i in range(n-2,-1,-1):
        for j in range(i+1,n):
            tmp += np.round( A[i,j] * x[j,0], num_dig )
        x[i,0] = np.round( (b[i,0] - tmp) / A[i,i], num_dig )
        tmp = 0
    return x

def gaussian_elimination_partial_pivoting(A, b, num_dig):
    """
    Rounding.
    """
    np_result = A**-1 * b
    print(np_result.T)
    n = A.shape[0]
    x = np.zeros((n,1))
    tmp = 0
    for k in range(n-1):
        M = k
        for m in range(k+1,n):
            if A[m,k] > A[M,k]: M = m
        A[[k,M]] = A[[M,k]]
        b[[k,M]] = b[[M,k]]
        for i in range(k+1,n):
            m = A[i,k] / A[k,k]
            A[i,:] = np.round( A[i,:] - m*A[k,:], num_dig )
            b[i,0] = np.round( b[i,0] - m*b[k,0], num_dig )
        print(np.hstack((A,b)))
    x[-1,0] = np.round( b[-1,0] / A[-1,-1], num_dig )
    for i in range(n-2,-1,-1):
        for j in range(i+1,n):
            tmp += np.round( A[i,j] * x[j,0], num_dig )
        x[i,0] = np.round( (b[i,0] - tmp) / A[i,i], num_dig )
        tmp = 0
    return x

A = np.matrix([[0.03, 58.9],[5.31, -6.10]])
b = np.matrix([[59.2], [47.0]])
result = gaussian_elimination_partial_pivoting(A, b, num_dig=3)
print(result.T)

print(np.linalg.norm(result-np.matrix([[10],[1]])))


"""

Path Plannting with B-Spline

author: Atsushi Sakai (@Atsushi_twi)

"""

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate as si

# parameter
N = 3  # B Spline order

def bspline_planning(x, y, sn):
    t = range(len(x))
    x_tup = si.splrep(t, x, k=N)
    y_tup = si.splrep(t, y, k=N)

    x_list = list(x_tup)
    xl = x.tolist()
    x_list[1] = xl + [0.0, 0.0, 0.0, 0.0]

    y_list = list(y_tup)
    yl = y.tolist()
    y_list[1] = yl + [0.0, 0.0, 0.0, 0.0]

    ipl_t = np.linspace(0.0, len(x) - 1, sn)
    rx = si.splev(ipl_t, x_list)
    ry = si.splev(ipl_t, y_list)

    return rx, ry

def main():
    print(__file__ + " start!!")
    # way points
    x = np.array([-1.0, 3.0, 4.0, 2.0, 1.0])
    y = np.array([0.0, -3.0, 1.0, 1.0, 3.0])
    sn = 100  # sampling number

    rx, ry = bspline_planning(x, y, sn)

    # show results
    plt.plot(x, y, '-og', label="Waypoints")
    plt.plot(rx, ry, '-r', label="B-Spline path")
    plt.grid(True)
    plt.legend()
    plt.axis("equal")
    plt.show()

if __name__ == '__main__':
    main()


"""
We want to use a similar code to part a, but modify the function
used at the beginning to give a van der Pol oscillation simulation through our plots
this time. We also want to vary the mu value in this new function
for 3 different values, and then plot the results along side our original
functions for reference
"""
import numpy as np
import matplotlib.pyplot as plt
#Question 1b
w = 1.0
mu = 0.1 #define the new constant
def f(r,t):
    x = r[0]
    v = r[1]
    fx = v
    #Modified van der Pol dvelocity equation
    fv = mu*(1-x**2)*v-(w**2)*x
    return np.array([fx,fv],float)
#Including the code from part a) to include in plot for reference
def g(r1,t):
    x = r1[0]
    v = r1[1]
    gx = v
    gv = -(w**2)*x
    return np.array([gx,gv],float)

a = 0.0
b = 60.0
N = 1000
h = (b-a)/N

#Create distinct position and velocity bins for regular SOH and van der Pol
time = np.arange(a,b,h)
#Van der Pol v and x
xvals = []
vvals = []
#SOH v and x
x1vals = []
v1vals = []

#Van der Pol RK4 ODE  solution as in part a
r = np.array([1.0,0.0],float)
for t in time:
    xvals.append(r[0])
    vvals.append(r[1])
    k1 = h*f(r,t)
    k2 = h*f(r+0.5*k1,t+ 0.5*h)
    k3 = h*f(r+0.5*k2, t +0.5*h)
    k4 = h*f(r + k3, t + h)
    r += (k1+2*k2+2*k3+k4)/6

#SOH RK$ ODE solution as in part a
r1 = np.array([1.0,0.0],float)
for t in time:
    x1vals.append(r1[0])
    v1vals.append(r1[1])
    k1 = h*g(r1,t)
    k2 = h*g(r1+0.5*k1,t+ 0.5*h)
    k3 = h*g(r1+0.5*k2, t +0.5*h)
    k4 = h*g(r1 + k3, t + h)
    r1 += (k1+2*k2+2*k3+k4)/6

#Plotting both functions
fig, graph = plt.subplots()
graph.plot(time, xvals, label = "Van der Pol")
graph.plot(time,x1vals, label = "SOH")
graph.set(xlabel = 'time (s)', ylabel = 'position (m)', title = 'position vs time, van der Pol mu = 0.1')
graph.legend()
plt.show()
fig, graph = plt.subplots()
plt.plot(xvals, vvals, label = "Van der Pol")
plt.plot(x1vals, v1vals, label = "SOH")
graph.set(ylabel = 'velocity (m/s)', xlabel = 'position (m)', title = 'phase space plot, van der Pol mu = 0.1')
graph.legend()
plt.show()

"""
from here on, the code is repeated for the next two desired values of mu, the only change is the constant
mu defined at the beginning, the rest is exactly as above
"""
#mu = 2
w = 1.0
mu = 2.0
def f(r,t):
    x = r[0]
    v = r[1]
    fx = v
    fv = mu*(1-x**2)*v-(w**2)*x
    return np.array([fx,fv],float)

a = 0.0
b = 60.0
N = 1000
h = (b-a)/N

time = np.arange(a,b,h)
xvals = []
vvals = []

r = np.array([1.0,0.0],float)
for t in time:
    xvals.append(r[0])
    vvals.append(r[1])
    k1 = h*f(r,t)
    k2 = h*f(r+0.5*k1,t+ 0.5*h)
    k3 = h*f(r+0.5*k2, t +0.5*h)
    k4 = h*f(r + k3, t + h)
    r += (k1+2*k2+2*k3+k4)/6

fig, graph = plt.subplots()
graph.plot(time, xvals, label = "Van der Pol")
graph.plot(time,x1vals, label = "SOH")
graph.set(xlabel = 'time (s)', ylabel = 'position (m)', title = 'position vs time, van der Pol mu = 2')
graph.legend()
plt.show()
fig, graph = plt.subplots()
plt.plot(xvals, vvals, label = "Van der Pol")
plt.plot(x1vals, v1vals, label = "SOH")
graph.set(ylabel = 'velocity (m/s)', xlabel = 'position (m)', title = 'phase space plot, van der Pol mu = 2')
graph.legend()
plt.show()

#mu = 5
w = 1.0
mu = 5.0
def f(r,t):
    x = r[0]
    v = r[1]
    fx = v
    fv = mu*(1-x**2)*v-(w**2)*x
    return np.array([fx,fv],float)

a = 0.0
b = 60.0
N = 1000
h = (b-a)/N

time = np.arange(a,b,h)
xvals = []
vvals = []

r = np.array([1.0,0.0],float)
for t in time:
    xvals.append(r[0])
    vvals.append(r[1])
    k1 = h*f(r,t)
    k2 = h*f(r+0.5*k1,t+ 0.5*h)
    k3 = h*f(r+0.5*k2, t +0.5*h)
    k4 = h*f(r + k3, t + h)
    r += (k1+2*k2+2*k3+k4)/6

fig, graph = plt.subplots()
graph.plot(time, xvals, label = "Van der Pol")
graph.plot(time,x1vals, label = "SOH")
graph.set(xlabel = 'time (s)', ylabel = 'position (m)', title = 'position vs time, van der Pol mu = 5')
graph.legend()
plt.show()
fig, graph = plt.subplots()
plt.plot(xvals, vvals, label = "Van der Pol")
plt.plot(x1vals, v1vals, label = "SOH")
graph.set(ylabel = 'velocity (m/s)', xlabel = 'position (m)', title = 'phase space plot, van der Pol mu = 5')
graph.legend()
plt.show()


from sympy import *
import math
from numpy import arange

class BrigeVeta:

    num_of_iteration = 0
    root

    # pass a function to me
    def __init__(self, function_formula, initial_x, ai, max_iterations, precision, x=Symbol('x')):
        self.function_formula = function_formula
        self.initial_x = initial_x
        self.ai = ai
        self.X = x
        self.max_iterations = max_iterations
        if max_iterations == 0:
            self.max_iterations = 50
        self.precision = precision
        if precision == 0:
            self.precision = 0.0001

    def compute_root(self):
            table = []
            fxi = float(self.function_formula.subs(self.X, self.initial_x))

            # create the table1
            table1 = [['i', 'Xi', 'F(Xi)', 'relative_error']]
            row = [0, self.initial_x, fxi, None]
            table1.append(row)
            max_pow = len(self.ai) - 1
            j = 0

            # if the initial guess is the root
            if self.function_formula.evalf(subs={self.X: self.initial_x}) < 1e-10 \
                    and self.function_formula.evalf(subs={self.X: self.initial_x}) > -1e-10:
                BrigeVeta.root = self.initial_x
                return [table1], self.initial_x, true

            while True:

                i = max_pow
                j = j + 1

                # create table2
                table2 = [['i', 'ai', 'bi', 'ci']]
                row = [max_pow, self.ai[0], self.ai[0], self.ai[0]]
                table2.append(row)

                i = i - 1
                bi = ci = self.ai[0]
                k = 1
                while i >= 0:

                    bi = bi * self.initial_x + self.ai[k]
                    if i > 0:
                        ci = ci * self.initial_x + bi
                        temp = ci
                    else:
                        temp = None

                    # add Row2 to the table2
                    row = [i, self.ai[k], bi, temp]
                    table2.append(row)

                    k = k + 1
                    i = i - 1

                #print(table2)
                table.append(table2)

                if ci == 0.0:
                    return table, 0.0, false

                iterative_x = self.initial_x - (bi / ci)

                # if the root is zero
                if iterative_x == 0.0:
                    if self.function_formula.evalf(subs={self.X: iterative_x}) < 1e-10 \
                            and self.function_formula.evalf(subs={self.X: iterative_x}) > -1e-10:

                        return [table], iterative_x, true
                    else:
                        relative_error = 1.0
                else:
                    relative_error = (iterative_x - self.initial_x) / iterative_x

                fxi = float(self.function_formula.subs(self.X, iterative_x))

                # add Row1 to the table1
                row = [j, iterative_x, fxi, math.fabs(relative_error)]
                table1.append(row)

                # break when reach max iteration or precision
                if (math.fabs(relative_error) <= self.precision) | (i >= self.max_iterations):
                    break

                if iterative_x < 1e-10 and iterative_x > -1e-10:
                    break

                self.initial_x = iterative_x

            print (table1)
            table.append(table1)
            BrigeVeta.root = iterative_x

            return table, iterative_x, true

    # to do call to check if root
    def is_root(self):
        try:
            if self.function_formula.evalf(subs={self.X: BrigeVeta.root}) < 1e-1 \
                    and self.function_formula.evalf(subs={self.X: BrigeVeta.root}) > -1e-1:
                return true
            else:
                return false
        except:
            return false

    def get_x_y(self):

        a = []
        b = []

        for x in range(-50, 50, 1):
            y = self.function_formula.evalf(subs={self.X: x})
            a.append(x)
            b.append(y)
        return a, b


import sympy as sp

# Symbols
a,x,y = sp.symbols('a x y')
A,X,MAX = sp.symbols('A X MAX')

# Attractor limits
x_min = (4*a**2 - a**3) / 16
x_max = a / 4

# Functions
f  = lambda x: a*x*(1-x)
g  = lambda x: (x - x_min) / (x_max - x_min)
gi_expr = sp.solve(g(x) - y, x)[0]
gi = lambda y_val: gi_expr.subs(y,y_val)

# Functions:
#  f  : Attr  -> Attr
#  g  : Attr  -> [0,1]
#  gi : [0,1] -> Attr
# g o f o gi : [0,1] -> [0,1]

F_expr = g(f(gi(x))).simplify()
F = lambda x_val: F_expr.subs(x,x_val)

# Parameter
# From [Pisarchik]: 3.57 < a < 4
a_min = sp.Rational(357,100)
a_max = sp.Rational(4)

# Map [0,1] -> [x_max,x_min]
h  = lambda a: (a - a_min) / (a_max - a_min)
hi_expr = sp.solve(h(a)-x,a)[0]
hi = lambda x_val: hi_expr.subs(x,x_val)

#
#FD(X).diff(C) = MAX*(1849*A**2 + 22102*A*MAX + 16049*MAX**2)/(1849*A**2 + 22102*A*MAX + 56049*MAX**2)
#

# Discretization of F
FD = lambda X: (MAX * F(X/MAX)).subs(a, hi(A/MAX)).simplify()
C  = [(FD(X).taylor_term(i,X) / X**i).simplify() for i in range(3)]

# Polynomial descriptor
num = tuple([c.as_numer_denom()[0] for c in C])
den = tuple([c.as_numer_denom()[1] for c in C])
ngcd = sp.gcd(sp.gcd(num[0],num[1]),num[2])
dgcd = sp.gcd(sp.gcd(den[0],den[1]),den[2])
pdesc = {
	'num' : [n/ngcd for n in num],
	'den' : [d/dgcd for d in den],
	'N'   : ngcd,
	'D'   : dgcd
}

# Iterator
class discrete_logistic:
	def __init__(self,bits,A_val):
		#pdesc = self.__generate_pdesc()
		sub = {MAX:2**bits-1,A:A_val}
		self.num  = [int(n.subs(sub).simplify()) for n in pdesc['num']]
		self.den  = [int(n.subs(sub).simplify()) for n in pdesc['den']]
		self.N    = int(pdesc['N'].subs(sub).simplify())
		self.D    = int(pdesc['D'].subs(sub).simplify())
		self.next = 0

	def step(self,X=None,n=1):
		if X is None: X = self.next
		for _ in range(n):
			X = (( self.num[0]      // self.den[0]) +
			     ((self.num[1]*X)   // self.den[1]) +
			     ((self.num[2]*X*X) // self.den[2])  )
			X = (X * self.N) // self.D
		self.next = X
		return X

#
#% Discretization
#syms A X MAX
#FD = simplify(subs(MAX*F,{a,x},{hi(A/MAX),X/MAX}));
#
#% Polynomial coefficients
#C = simplify(coeffs(F,x));
#CD = simplify(subs(MAX*C,{a,x},{hi(A/MAX),X/MAX}));
#
#


#!/usr/bin/env python

import numpy as np
import scipy.io.wavfile as wav_io

def create_sin(time, freq, ampl, phase):
    return ampl*np.sin(2.0*np.pi*freq*time + phase)

def normalize(signal, ampl):
    min_signal = np.min(signal)
    max_signal = np.max(signal)
    return (signal*ampl)/(max_signal - min_signal)

if __name__ == '__main__':
    from argparse import ArgumentParser
    import sys
    INPUT_ERROR = 1
    arg_parser = ArgumentParser(description='create a WAV file')
    arg_parser.add_argument('--duration', type=float, default=2.0,
                            help='signal duractino in secons')
    arg_parser.add_argument('--rate', type=int, default=44100,
                            help='sample rate in Hz')
    arg_parser.add_argument('--base', type=int, default=128,
                            help='base line amplitude')
    arg_parser.add_argument('--amplitude', type=float, default=30.0,
                            help='amplitued to normalize signal to')
    specs_group = arg_parser.add_argument_group()
    specs_group.add_argument('--freqs', metavar='FREQ', type=float,
                             nargs='+',
                             help='frequencies of sinusiodal components')
    specs_group.add_argument('--ampls', metavar='AMPL', type=float,
                             nargs='+',
                             help='amplitudes of sinusiodal components')
    specs_group.add_argument('--phases', metavar='PHASE', type=float,
                             nargs='*',
                             help='phases of sinusiodal components')
    arg_parser.add_argument('--specs_file', help='Wave specification file')
    arg_parser.add_argument('--wav', help='name of WAV file to write '
                                          'the signal to')
    arg_parser.add_argument('--out', action='store_true',
                            help='print signal to standard output')
    options = arg_parser.parse_args()
    if options.specs_file:
        pass
    else:
        if len(options.freqs) != len(options.ampls):
            msg = '# error: {0:d} frequencies for {1:d} amplitudes\n'
            sys.stderr.write(msg.format(len(options.freqs),
                                        len(options.aompls)))
            sys.exit(INPUT_ERROR)
        freqs = np.array(options.freqs)
        ampls = np.array(options.ampls)
        if options.phases is not None:
            if len(options.phase) != len(options.freqs):
                msg = '# error: {0:d} frequencies for {1:d} phases\n'
                sys.stderr.write(msg.format(len(options.freqs),
                                            len(options.aompls)))
                sys.exit(INPUT_ERROR)
            phases = np.array(options.phases)
        else:
            phases = np.zeros(len(freqs))
    time = np.arange(options.rate*options.duration)/options.rate
    signal = np.zeros(len(time))
    for freq, ampl, phase in zip(freqs, ampls, phases):
        signal += create_sin(time, freq, ampl, phase)
    signal = options.base + normalize(signal, options.amplitude)
    if options.out:
        for t, s in zip(time, signal):
            print('{0:.6f}\t{1:.6f}'.format(t, s))
    if options.wav:
        wav_data = np.array(signal, dtype='uint8')
        wav_io.write(options.wav, options.rate, wav_data)


from scipy import integrate, linalg
import numpy as np

# 積分
# 2x + 5のこと
def func(x):
    return 2*x + 5

# 2x + 5を積分すると
# 2 * 1/2 * x^2 + 5 * x + c
# すなわち
# x^2 + x^2 + c
# 5を代入すると
# 積分結果は50
# 誤差(cのこと)

res, err = integrate.quad(func, 0, 5)

print("積分結果：{0}, 誤差：{1}".format(res, err))

# 行列
# numpy
narray = np.array([[1, 2], [3, 4]])

# numpyでリストより生成した配列(行列)の逆行列を取得する
inv_narray = linalg.inv(narray)
print("narrayの逆行列：{}".format(inv_narray))

# 逆行列より行列式を取得する
res_det = linalg.det(inv_narray)
print("inv_narrayの行列式：{}".format(res_det))

# 逆行列よりノルムを取得する
inv_narray_norm = linalg.norm(inv_narray)
print("inv_narrayのノルム：{}".format(inv_narray_norm))


"""
a set of cost functions for Neural Network layers.
"""

import theano.tensor as T

def l1_norm(w):
    """
    Returns L1 norm of the given matrix (w).
    L1 norm is simply sum of a matrix elements.

    @input: w, a theano shared variable.
    @output: L1 norm of w
    """

    return abs(w).sum()

def l2_norm(w):
    """
    Returns L2 norm of the given matrix (w).
    L2 norm of a matrix is simply square root of the sum of square of elements of the matrix.
    In an other word, it's length for a matrix.

    @input: w, a theano shared variable.
    @output: L2 norm of w
    """

    return T.sqrt((w ** 2).sum())

def l2_norm_sqr(w):
    """
    Returns square of L2 norm of the given matrix (w).
    square of L2 norm of a matrix is simply the sum of square of elements of the matrix.

    @input: w, a theano shared variable.
    @output: L2 norm of w
    """

    return (w ** 2).sum()

# TODO
# add contractive cost function.


import numpy as np

def correlation(x, y):

    x = rearrange_activations(x)
    y = rearrange_activations(y)

    # Subtruct means
    x_norm = x - x.mean(axis=1)[:, None]
    y_norm = y - y.mean(axis=1)[:, None]

    # Sum of squares
    ss_x = (x_norm**2).sum(axis=1)
    ss_y = (y_norm**2).sum(axis=1)

    # Final form
    # (x-E[x]) * (y-E[y])
    # -------------------
    #  sqrt(E[x] * E[y])
    numerator = np.dot(x_norm, y_norm.T)
    denominator = np.sqrt(np.dot(ss_x[:, None], ss_y[None]))
    correlation = numerator / denominator

    return correlation.T

def rearrange_activations(activations):
    is_convolution = len(activations.shape) == 4
    permutation = [1, 2, 3, 0] if is_convolution else [1, 0]
    activations = np.transpose(activations, axes=permutation)
    new_shape = (activations.shape[0], -1)
    reshaped_activations = activations.reshape(*new_shape)
    return reshaped_activations

if __name__ == '__main__':

    # Shape
    batch_size = 100
    n_channels = 64
    spatial_size = (5, 7)
    shape = (batch_size, n_channels, *spatial_size)

    a = np.random.random(shape)

    # Corr 1
    b = a * 3 - 10
    corr = correlation(a, b)
    print(f"1 corr: {corr.shape} {np.diag(corr).mean()}")

    # Corr 0
    b = np.random.random(shape)
    corr = correlation(a, b)
    print(f"0 corr: {corr.shape} {np.diag(corr).mean()}")

    # Corr -1
    b = -20 * a + 103
    corr = correlation(a, b)
    print(f"-1 corr: {corr.shape} {np.diag(corr).mean()}")


# Ugalde Carreño Paulina 19141209

# Importamos las siguientes
import numpy as np
import matplotlib.pyplot as plt

# Definimos un arreglo para almacenar los puntos
puntos = []
# Abrimos nuestro archivo txt donde guardamos nuestros puntos
with open("Puntos.txt", "r") as File:
	for lineas in File:
        # Usamos lineas.split() para separar cada linea usando el caracter de espacio en blanco,
        # por lo que obtenemos una lista donde cada elemento es una de los numeros de esa línea.
        # Usamos el método extend para copiar todo el contenido de esa lista de numeros a otra lista llamada puntos
		puntos.extend(lineas.split())
# Cerramos el archivo
File.close()

# Listas que almacenaran las coordenadas en x e y respectivamente
CoordenadasX = []
CoordenadasY = []

# Extraemos las coordenadas de la lista de puntos
for i in range(len(puntos)):
    if i%2 ==0:
        CoordenadasX.append(float(puntos[i]))
    else:
        CoordenadasY.append(float(puntos[i]))

# Imprimimos las coordenadas de los puntos en el formato (a,b)
print("Los puntos son: ")
for i in range(len(CoordenadasX)):
    print("("+str(CoordenadasX[i])+" , "+str(CoordenadasY[i])+")")

# Armamos la matriz nxn de la expresion 4 del PDF
def formarMatriz():
    # Obtenemos la cantidad de puntos
    N = len(CoordenadasX)
    # Armamos la matriz
    for i in range(filas):
        for j in range(filas):
            # La primera fila la formamos por separado
            if i == 0 and j == 0:
                    matriz[i][j] = N
            elif i==0:
                for k in range(len(CoordenadasX)):
                    matriz[i][j] += pow(CoordenadasX[k],j)
            # Las demas filas las formamos juntas
            else:
                for k in range(len(CoordenadasX)):
                    matriz[i][j] += pow(CoordenadasX[k],(i+j))

# Armamos las soluciones de la matriz 4 del PDF
def formarSoluciones():
    for i in range(len(soluciones)):
        for k in range(len(CoordenadasY)):
            soluciones[i] += pow(CoordenadasX[k],i)*CoordenadasY[k]

# Imprimir la matriz
def imprimirMatriz(matriz, filas):
    for i in range(filas):
        print("[", end=" ")
        for j in range(filas):
            #imprime el numero redondeado por 2 cifras
                print(round(matriz[i][j],2), end="\t")
        print("| "+str(round(soluciones[i],2))+" ]")

# Metodo para imprimir el polinomio
def imprimirPolinomio(x):
    polinomio = f"{round(x[-1],3)}x^{len(x)-1} "

    for i in reversed(range(2, len(x)-1)):
        if x[i] < 0:
            polinomio += f"{round(x[i], 3)}x^{i} "
        else:
            polinomio += f"+ {round(x[i], 3)}x^{i} "

    if x[1] < 0:
        polinomio += f"{round(x[1], 3)}x "
    else:
        polinomio += f"+ {round(x[1], 3)}x "

    if x[0] < 0:
        polinomio += f"{round(x[0], 3)} "
    else:
        polinomio += f"+ {round(x[0], 3)} "

    print("El polinomio es: ")
    print(polinomio)

# Metodo para sacar la recta de regresion
def minimosCuadrados(CoordenadasX, CoordenadasY):
    sumx = sumy = sumxy = sumx2 = 0
    n = len(CoordenadasX)  # Cantidad de puntos

    for i in range(n):     # Se ejecutan cada una de las sumas
        sumx += CoordenadasX[i] # Suma de las coordenadas en x
        sumy += CoordenadasY[i] # Suma de las coordenadas en y
        sumxy += CoordenadasX[i]*CoordenadasY[i]    # Suma de los productos x*y
        sumx2 += pow(CoordenadasX[i], 2)            # Suma de los cuadrados de x

    promx = sumx/n  # Promedio de x
    promy = sumy/n  # Promedio de y

    # Calcula la pendiente (a1) - Tomado de Chapra
    a1 = (n * sumxy - sumx*sumy)/(n * sumx2 - sumx**2)
    a0 = promy - a1*promx  # Calcula el intercepto (a0) - Tomado de Chapra

    return a1, a0

# -----------------------------------------MAIN----------------------------------------------------------
grado = int(input("Grado del polinomio que deseas ajustar (>0): "))

# Polinomio de grado n, el sistema es (n+1)*(n+1)
filas = grado + 1

# Declaramos nuestra matriz vacia
matriz = []
# Declaramos nuestro vector de soluciones
soluciones = []
# Construimos nuestra matriz, introduciendo 0 en ella e introducimos 0 en el array
for i in range(filas):
    matriz.append([0]*filas)
    soluciones.append(0)

# Mandamos a llamar nuestros metodos
formarMatriz()
formarSoluciones()
imprimirMatriz(matriz, filas)

# Resolvemos el sistema de ecuaciones
x = np.linalg.solve(matriz,soluciones)

# Imprimimos nuestros resultados
print("===========================================", end="\n")
# Imprimimos el polinomio
imprimirPolinomio(x)
# Mandamos llamar nuestro metodo para obtener los valores de la recta
a1, a0 = minimosCuadrados(CoordenadasX, CoordenadasY)
# Imprimimos la recta de regresion
print("La recta de regresion es: ")
print("y = " + str(a1) + "x + " + str(a0))  # y = a1x + a0
print("===========================================")

t = np.poly1d([a1, a0])
#Propiedades de la grafica
plt.scatter(CoordenadasX, CoordenadasY, alpha=1, color="orange")
# Grafica de la recta de regresion
plt.plot(CoordenadasX, a1*np.array(CoordenadasX) + a0, label=t)
plt.axhline(0, color='black')
plt.axvline(0, color='black')
plt.grid()
plt.show()


# Digital Signal Processing - Lab 1 - Part 1
# Christos Dimopoulos - 03117037
# Dimitris Dimos - 03117165

import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
import scipy.signal
import sounddevice as sd
import soundfile as sf

plt.close('all')

# Part 1

#1.1 Defining 10 telephone tones
n = np.arange(0,1000,1) #number of samples

Frow = np.array([0.5346, 0.5906, 0.6535, 0.7217])
Fcolumn = np.array([0.9273, 1.0247, 1.1328])

def digit_tone(row, column):
    digit = np.sin(Frow[row]*n) + np.sin(Fcolumn[column]*n)
    return digit

digit1 = digit_tone(0,0)
digit2 = digit_tone(0,1)
digit3 = digit_tone(0,2)
digit4 = digit_tone(1,0)
digit5 = digit_tone(1,1)
digit6 = digit_tone(1,2)
digit7 = digit_tone(2,0)
digit8 = digit_tone(2,1)
digit9 = digit_tone(2,2)
digit0 = digit_tone(3,1)

counter=0
counter = counter+1
plt.figure(counter)
plt.plot(n,digit5, label ='d5[n] = sin(0.5906n)+sin(1.0247n)')
plt.xlabel('Discrete Time n')
plt.ylabel('d5[n]')
plt.title('Tone of Digit 5')
plt.legend()
plt.show()

#1.2 Computing DFT of digits d4[n] and d6[n]
counter = counter+1
plt.figure(counter)
DFT4 = np.fft.fft(digit4)
f = np.fft.fftfreq(1000, 1/1000)
plt.plot(f,np.abs(DFT4),'b', label='|F{sin(0.5906n)+sin(0.9273n)}|')
plt.xlabel('Sample frequency k')
plt.ylabel('|D4(k)|')
plt.title('DFT of tone d4[n]')
plt.legend()
plt.show()

counter = counter+1
plt.figure(counter)
DFT6 = np.fft.fft(digit6)
f = np.fft.fftfreq(1000, 1/1000)
plt.plot(f,np.abs(DFT6),'b', label='|F{sin(0.5906)+sin(1.1328)}|')
plt.xlabel('Sample frequency k')
plt.ylabel('|D6(k)|')
plt.title('DFT of tone d6[n]')
plt.legend()
plt.show()

#1.3 Synthesizing tone sequence
#AM1 = 03117037
#AM2 = 03117165
#Digit_Seq = AM1+AM2 = 06234202
digits = 8
samples = 1000*digits + 100*(digits-1) #including 100 zeros between tones
n = np.arange(0,samples,1) #number of samples for 8 digits
tone_seq = np.zeros((samples))

tone_seq[0:1000] = digit0
tone_seq[1100:2100] = digit6
tone_seq[2200:3200] = digit2
tone_seq[3300:4300] = digit3
tone_seq[4400:5400] = digit4
tone_seq[5500:6500] = digit2
tone_seq[6600:7600] = digit0
tone_seq[7700:8700] = digit2

#Plot tone sequence in time
counter = counter+1
plt.figure(counter)
n = np.arange(0,8700,1)
plt.plot(n,tone_seq,'b', label ='Tone Sequence: 06234202')
plt.xlabel('Discrete Time n')
plt.ylabel('Tone_Sequence[n]')
plt.title('Tone Sequence of 8 tones')
plt.legend()
plt.show()

#Plor DFT of tone sequence
counter = counter+1
plt.figure(counter)
y = np.fft.fft(tone_seq)
f = np.linspace(-np.pi, np.pi,8700)
f = np.fft.fftshift(f)
plt.plot(f,np.abs(y),'b', label ='Tone Sequence: 06234202')
plt.xlabel('Radial frequency ω')
plt.ylabel('|F(ω)|')
plt.title('DFT of tone sequence')
plt.legend()
plt.show()

# Record tone sequence
sf.write('tone_sequence.wav', tone_seq, 3000)
sd.play(tone_seq,3000)

#1.4 Windowed FFT

def windowed_dft(i,j):
    n = 4096 # for more smooth result
    #Rectangular Window:
    window_rect = sp.signal.get_window("boxcar", 1000, 'true') #equivalent to no window at all
    #Hamming Window:
    window_hamming = sp.signal.get_window("hamming", 1000, 'true')

    t = np.arange(0, 1, step=1/1000.)

    #Slide in time the rectangular window signal:
    windowed = np.zeros((int(np.size(tone_seq))))
    windowed[i:j] = window_rect
    s = tone_seq*windowed
    s = s[i:j]

    #Rectangular Windowed DFT:
    w = np.fft.rfft(s, n=n)
    freqs = np.fft.rfftfreq(n, d=t[1] - t[0]) # DFT sample frequencies

    #Slide in time the Hamming window signal:
    windowed = np.zeros((int(np.size(tone_seq))))
    windowed[i:j] = window_hamming
    s = tone_seq*windowed
    s = s[i:j]

    #Hamming Windowed DFT:
    v = np.fft.rfft(s, n=n)

    plot = plt.subplot(2, 1, 1)
    plt.plot(freqs, 20*np.log10(np.abs(w)), 'b', label='Rectangular Window')
    plt.xlabel('k = 1000ω/2π sample frequencies')
    plt.ylabel('DFT (logarithmic)')
    plt.legend()

    plt.subplot(2, 1, 2)
    plt.plot(freqs, 20*np.log10(np.abs(v)), 'b', label='Hamming Window')
    plt.xlabel('k = 1000ω/2π sample frequencies')
    plt.ylabel('DFT (logarithmic)')
    plt.legend()
    return plot

#Plot windowed DFT for all 8 digits of tone_sequence:

counter = counter+1
plt.figure(counter)
f4 = windowed_dft(0,1000)
plt.suptitle('DFT of samples 0-1000 (Tone of Digit 0)')
plt.show()

counter = counter+1
plt.figure(counter)
f4 = windowed_dft(1100,2100)
plt.suptitle('DFT of samples 1100-2100 (Tone of Digit 6)')
plt.show()

counter = counter+1
plt.figure(counter)
f4 = windowed_dft(2200,3200)
plt.suptitle('DFT of samples 2200-3200 (Tone of Digit 2)')
plt.show()

counter = counter+1
plt.figure(counter)
f4 = windowed_dft(3300,4300)
plt.suptitle('DFT of samples 3300-4300 (Tone of Digit 3)')
plt.show()

counter = counter+1
plt.figure(counter)
f4 = windowed_dft(4400,5400)
plt.suptitle('DFT of samples 4400-5400 (Tone of Digit 4)')
plt.show()

counter = counter+1
plt.figure(counter)
f4 = windowed_dft(5500,6500)
plt.suptitle('DFT of samples 5500-6500 (Tone of Digit 2)')
plt.show()

counter = counter+1
plt.figure(counter)
f4 = windowed_dft(6600,7600)
plt.suptitle('DFT of samples 6600-7600 (Tone of Digit 0)')
plt.show()

counter = counter+1
plt.figure(counter)
f4 = windowed_dft(7700,8700)
plt.suptitle('DFT of samples 7700-8700 (Tone of Digit 2)')
plt.show()

#1.5 DFT sample frequency - value of k

def freq_to_k(frequency, N_samples):
    k = np.round((frequency*N_samples)/(2*np.pi))
    return k

def k_to_freq(k, N_samples):
    frequency = (2*np.pi*k)/N_samples
    return frequency

N = 1000 #number of samples
k_row0 = freq_to_k(Frow[0],N)
k_row1 = freq_to_k(Frow[1],N)
k_row2 = freq_to_k(Frow[2],N)
k_row3 = freq_to_k(Frow[3],N)

k_column0 = freq_to_k(Fcolumn[0],N)
k_column1 = freq_to_k(Fcolumn[1],N)
k_column2 = freq_to_k(Fcolumn[2],N)

k_rows = np.array([k_row0, k_row1, k_row2, k_row3])
k_columns = np.array([k_column0, k_column1, k_column2])
print("Values k of rows = ",k_rows)
print("Values k of columns = ", k_columns)

#1.6 Decode Function

#Function that finds the second greatest element of an array
def secondmax(arr,arr_size):
    # There should be atleast
    # two elements
    first = second = -2147483648
    for i in range(arr_size):

        # If current element is
                # smaller than first
        # then update both
                # first and second
        if (arr[i] > first):

            second = first
            first = arr[i]

        # If arr[i] is in
        # between first and
        # second then update second
        elif (arr[i] > second and arr[i] != first):
            second = arr[i]

    if (second == -2147483648):
        print("There is no second largest element")
    else:
        return second

#Function that calculates the number of zeros between consecutive tones:
def zeros_between_tones(sequence,tones):
    zerosbetween = np.zeros((tones+2))
    for i in range(tones+1):
        samples = sequence[1000*i+int(zerosbetween[i]): 1000*(i+1)+int(zerosbetween[i])]
        zerosbetween[i] = np.size(samples) - np.size(np.nonzero(samples))
    return zerosbetween[0:tones]

def ttdecode(sequence):

    #Analyze input tone sequence into separate tones of 1000 samples:
    number_of_tones = round(np.count_nonzero(sequence)/1000)
    tone = np.zeros((number_of_tones,1000))
    zerosbetween = zeros_between_tones(sequence,number_of_tones)
    for i in range(number_of_tones):
        tone[i,:] = sequence[(1000*i+int(np.sum(zerosbetween[0:i]))):(1000*i+int(np.sum(zerosbetween[0:i]))+1000)]

    #Calculate Windowed DFT of each tone:
    n = 1000 # for more smooth result
    t = np.arange(0, 1, step=1/1000.)
    freqs = np.fft.rfftfreq(n, d=t[1] - t[0])

    #Hamming Window:
    window_hamming = sp.signal.get_window("hamming", 1000, 'true')

    #Calculate Energy = |X[k]|^2 of tones:
    energy = np.zeros((number_of_tones,2049))
    max_energy = np.zeros((number_of_tones)) #max energy value
    secondmax_energy = np.zeros((number_of_tones)) #second max energy value
    k_ofmax = np.zeros((number_of_tones,2))
    k_ofmax2 = np.zeros((number_of_tones,2))

    for i in range(number_of_tones):
        s = tone[i,:]
        #Hamming Windowed DFT:
        DFT = np.fft.rfft(s * window_hamming, n=n)
        energy = np.square(np.abs(DFT))
        max_energy = np.max(energy)
        secondmax_energy = secondmax(energy,np.size(energy))
        k_ofmax[i] = np.where(energy == max_energy)
        k_ofmax2[i] = np.where(energy == secondmax_energy)

    #Define positions in which we have maximum energy for each tone
    positions = np.zeros((number_of_tones,2))
    positions[:,0] = k_ofmax[:,0]
    positions[:,1] = k_ofmax2[:,1]

    #Transform positions into k values
    kvalues = np.zeros((number_of_tones,2))
    for i in range(number_of_tones):
        kvalues[i,0] = freqs[int(positions[i,0])]
        kvalues[i,1] = freqs[int(positions[i,1])]

    #Accepted k values according to given table
    accepted_k = np.concatenate((k_rows, k_columns), axis=None)

    #Function that finds k values closest to the accepted
    def closest(array, K):
         idx = (np.abs(array - K)).argmin()
         return array[idx]

    #Create array of accepted k values according to table given
    for i in range(number_of_tones):
        kvalues[i,0] = closest(accepted_k, kvalues[i,0])
        kvalues[i,1] = closest(accepted_k, kvalues[i,1])

    #Function that accords k values to tone digits
    digitseq = np.zeros(number_of_tones)
    def ktodigit(i, j):
        if (i==k_row0 and j==k_column0)or(j==k_row0 and i==k_column0):
            return '1'
        elif (i==k_row0 and j==k_column1)or(j==k_row0 and i==k_column1):
            return '2'
        elif (i==k_row0 and j==k_column2)or(j==k_row0 and i==k_column2):
            return '3'
        elif (i==k_row1 and j==k_column0)or(j==k_row1 and i==k_column0):
            return '4'
        elif (i==k_row1 and j==k_column1)or(j==k_row1 and i==k_column1):
           return '5'
        elif (i==k_row1 and j==k_column2)or(j==k_row1 and i==k_column2):
            return '6'
        elif (i==k_row2 and j==k_column0)or(j==k_row2 and i==k_column0):
            return '7'
        elif (i==k_row2 and j==k_column1)or(j==k_row2 and i==k_column1):
            return '8'
        elif (i==k_row2 and j==k_column2)or(j==k_row2 and i==k_column2):
            return '9'
        elif (i==k_row3 and j==k_column1)or(j==k_row3 and i==k_column1):
            return '0'

    for i in range(number_of_tones):
        digitseq[i] = ktodigit(kvalues[i,0],kvalues[i,1])

    return digitseq

tonesequence = ttdecode(tone_seq)
print('AM1+AM2 =', tonesequence)

#1.7 Load easySig.npy and hardSig.npy
easysig = np.load('easySig.npy')
easysequence = ttdecode(easysig)
print('Easy Tone Sequence: ', easysequence)

hardsig = np.load('hardSig.npy')
hardsequence = ttdecode(hardsig)
print('Hard Tone Sequence: ', hardsequence)


########################################################
# Rodrigo Leite - drigols                              #
# Last update: 02/03/2021                              #
########################################################

from scipy.sparse import csr_matrix
from numpy import count_nonzero
from numpy import array

# Create dense matrix
dense_matrix = array(
  [
    [1, 0, 0, 1, 0, 0],
    [0, 0, 2, 0, 0, 1],
    [0, 0, 0, 2, 0, 0]
  ]
)

# Convert to sparse matrix (CSR method)
sparse_matrix = csr_matrix(dense_matrix)
print("Sparse Matrix:\n", sparse_matrix)
print("Sparse Matrix shape:", sparse_matrix.shape)

# Reconstruct dense matrix
dense_matrix_two = sparse_matrix.todense()
print("\nDense Matrix:\n", dense_matrix_two)
print("Dense Matrix shape:\n", dense_matrix_two.shape)

# Calculate sparsity
sparsity = 1.0 - count_nonzero(dense_matrix) / dense_matrix.size
print("\nMatrix Sparsity", sparsity)


import numpy as np
from numba import jit

@jit(nopython=True)
def ewma_span(x, span=None):
    """Returns the exponentially weighted moving average of x.

    Parameters
    ----------
    x : array-like
        input array
    alpha : float, optional
        value between 0 and 1
    span : float, optional
        similar to window size in rolling mean

    Returns
    -------
    ewma : numpy array
        the exponentially weighted moving average

    Example:
    --------
        n = 120
        span = 120
        rev_alpha = 1.0 - (2.0 / (span+1.0))
        exponents = np.arange(n-1, -1, -1)
        weights = [rev_alpha ** e for e in exponents]
        plt.plot(weights)

    """
    alpha = 2.0 / (span + 1.0)

    n = len(x)
    ewa = [x[0]]
    for i in range(1, n):
        e = alpha * x[i] + (1 - alpha) * ewa[i - 1]
        ewa.append(e)

    assert len(ewa) == n

    return np.array(ewa)

def ewma(x, alpha=None, span=None):
    """Returns the exponentially weighted moving average of x.

    Parameters
    ----------
    x : array-like
        input array
    alpha : float, optional
        value between 0 and 1
    span : float, optional
        similar to window size in rolling mean

    Returns
    -------
    ewma : numpy array
        the exponentially weighted moving average

    Example:
    --------
        n = 120
        span = 120
        rev_alpha = 1.0 - (2.0 / (span+1.0))
        exponents = np.arange(n-1, -1, -1)
        weights = [rev_alpha ** e for e in exponents]
        plt.plot(weights)

    """
    if alpha is None and span is None:
        raise ValueError('At span or alpha has to be given.')
    if alpha is not None and span is not None:
        raise ValueError('Either give alpha or span, not both.')
    if span is not None:
        alpha = 2.0 / (span + 1.0)
    if alpha is not None:
        if not (0.0 < alpha < 1.0):
            raise ValueError('Alpha in wrong range: ', alpha)
    # coerce x to an array
    if not isinstance(x, np.ndarray):
        x = np.array(x)

    x = np.squeeze(x)
    if len(x.shape) != 1:
        raise ValueError('Must be 1-D, but it is: ', x.shape, x.size)
    n = x.size
    # create an initial weight matrix of (1-alpha), and a matrix of powers
    # to raise the weights by
    w0 = np.ones(shape=(n, n)) * (1 - alpha)
    p = np.vstack([np.arange(i, i - n, -1) for i in range(n)])
    # create the weight matrix
    w = np.tril(w0 ** p, 0)
    # calculate the ewma
    return np.dot(w, x[::np.newaxis]) / w.sum(axis=1)


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

"""
在这一部分中，您将实现多变量线性回归来预测房价。假设你在卖房子，你想知道一个好的市场价格是多少。
一种方法是首先收集最近售出的房子的信息，并制作一个房价模型。
文件ex1data2.txt包含俄勒冈州波特兰市房价的培训集。第一栏是房子的大小（平方英尺），第二栏是
卧室的数量，第三栏是房子的价格。ex1_multi.py脚本已设置为帮助您逐步完成此练习。
"""

import numpy as np

from data_processing import feature_normalize
from linear import LinearReg
from model_selection import linear_error

if __name__ == '__main__':
    'Part1: Feature Normalization'

    data = np.loadtxt('ex1data2.txt', delimiter=',')
    X = data[..., :2]
    y = data[..., 2:]
    m = data.shape[0]

    print('First 10 examples from the dataset: ')
    for i in range(10):
        print(' x = [%.0f %.0f], y = %.0f' % (X[i, 0], X[i, 1], y[i]))

    print('\nNormalizing Features ...')
    X, mean, std = feature_normalize(X)
    print('10 examples from featureNormalize(X):')
    for i in range(10):
        print('x = [%.0f %.0f]' % (X[i, 0], X[i, 1]))

    # Add intercept term to X
    X = np.hstack((np.ones((m, 1)), X))

    'Part2: Gradient Descent'

    print('\nRunning gradient descent ...\n')
    alpha = 0.1
    num_iter = 300

    li = LinearReg(max_iter=num_iter)
    theta = li.train(X, y)
    print('Theta computed from gradient descent:')
    print(theta)
    print('The mean squared error from gradient descent:')
    li.mean_row = mean
    li.std_row = std
    tmp = li.predict(X)
    for i in range(10): print(tmp[i], y[i])
    print(linear_error(tmp, y), '\n')

    # plt.plot([i for i in range(1, J_history.shape[0] + 1)], J_history, '-b', linewidth=2)
    # plt.xlabel('Number of iterations')
    # plt.ylabel('Cost J')
    # plt.show()

    price = li.predict(np.array([1650, 3]))
    print('Predicted price of a 1650 sq-ft, 3 br house (using gradient descent):\n $%f' % price)

    'Part3: Normal Equations'

    print('\nSolving with normal equations...')
    data = np.loadtxt('ex1data2.txt', delimiter=',')
    X = data[..., :2]
    y = data[..., 2:]

    # X = np.hstack((np.ones((m, 1)), X))
    # theta = lr.normal_eqn(X, y)
    li.method = 'normal'
    theta = li.train(X, y)
    print('Theta computed from the normal equations: ')
    print(theta)

    price = li.predict(np.array([1650, 3]))
    print('Predicted price of a 1650 sq-ft, 3 br house (using normal equation):\n $%f' % price)


import numpy as np
import rungekutta
import matplotlib.pyplot as plt
import sys

m = 0.0
x_0 = 0.0
h = 0.0
N = 0

for i in range(0, len(sys.argv)):
    ##if (sys.argv[i] == "-k"):
      ##  k = float(sys.argv[i+1])
        ##i += 1
    if (sys.argv[i] == "-m"):
        m = float(sys.argv[i+1])
        i += 1
    elif (sys.argv[i] == "-x_0"):
        x_0 = float(sys.argv[i+1])
        i += 1
    elif (sys.argv[i] == "-h"):
        h = float(sys.argv[i+1])
        i += 1
    elif (sys.argv[i] == "-N"):
        N = int(sys.argv[i+1])
        i += 1

k = (4*np.pi**2)*m

def f(t, y):
    if (len(y) != 2):
        print("Improper y array size in function f(t,y). Can only be 2 elements.")
    F = np.zeros(2)
    F[0] = y[1]
    F[1] = -(k/m)*y[0]
    return F

t = np.zeros(N)
x = np.zeros(N)
v = np.zeros(N)
KE = np.zeros(N)        ##kinetic energy
PE = np.zeros(N)        ##potential energy
E_total = np.zeros(N)
x_an = np.zeros(N)
v_an = np.zeros(N)
err = 10**-8

y = np.zeros(2)
y[0] = x_0

for i in range(0, N):
    t[i] = i*h + h
    y = rungekutta.rk4(t[i], y, h, f)
    x[i] = y[0]
    v[i] = y[1]
    KE[i] = 0.5*m*y[1]**2       ##used to track kinetic energy for the assignment
    PE[i] = 0.5*k*y[0]**2       ##used to track potential energy for the assignment
    E_total[i] = KE[i] + PE[i]
    x_an[i] = x_0*np.cos(2*np.pi*t[i])                ## analytical solution for position
    v_an[i] = -x_0*2*np.pi*np.sin(2*np.pi*t[i])        ## analytical solution for velocity

##    if (np.abs(x_an[i] - x[i])/x_an[i] >= err):
##        print("The difference between the analytical and numerical solutions for position is too large")
##        break
##    elif (np.abs(x_an[i] - x[i])/x_an[i] < err):
##        print("This time step produces an accurate result for the position.")

##    if (np.abs((v_an[i] - v[i])/v_an[i]) >= err):
##        print("The difference between the analytical and numerical solutions for velocity is too large")
##        break
##    elif (np.abs((v_an[i] - v[i])/v_an[i]) < err):
##        print("This time step produces an accurate result for the velocity.")

#if (np.abs(np.average(x_an) - np.average(x))/np.average(x_an) >= err):
#    print("The difference between the analytical and numerical solutions for position is too large")

#elif (np.abs(np.average(x_an) - np.average(x))/np.average(x_an) < err):
 #   print("This time step produces an accurate result for the position.")

#if (np.abs(np.average(v_an) - np.average(v))/np.average(v_an) >= err):
#    print("The difference between the analytical and numerical solutions for velocity is too large")

#elif (np.abs(np.average(v_an) - np.average(v))/np.average(v_an) < err):
#    print("This time step produces an accurate result for the velocity.")

average = sum(E_total)/len(E_total)
print(average)
standarddev = np.std(E_total)
print(standarddev)

rk4graphposition = plt.plot(t, x, 'o')
plt.ylabel('Position')
plt.xlabel('Time')
plt.axis([0, 10, -6, 6])
plt.savefig('rk4graphposition.pdf')
##rk2analytical = plt.plot(t, x_an, 'o')
##plt.axis([0, 10, -6, 6])
#plt.savefig('rk2positioncompare.pdf')
plt.close()

rk4graphvelocity = plt.plot(t, v, 'o')
plt.ylabel('Velocity')
plt.xlabel('Time')
plt.axis([0, 10, -40, 40])
plt.savefig('rk4graphvelocity.pdf')
##rk2analyticalV = plt.plot(t, v_an,'o')
##plt.axis([0, 10, -40, 40])
##plt.savefig('rk2velocitycompare.pdf')

f = open("rk4spring.dat", "w")
for i in range(0, N):
    f.write(str(t[i]) + " " + str(x[i]) + " " + str(v[i]) + " " + str(KE[i]) + " " + str(PE[i]) + " " + str(E_total[i]) + "\n")
f.close()


import numpy as np
import matplotlib.pyplot as mpl

# -------------------------------------|Declaração de funções|-------------------------------------#
def g0(x):
    return 1

def g1(x):
    return x

def g2(x):
    return x ** 2

def g3(x):
    return x ** 3

def produto_interno(a, b, x, n, tipo):

    res = 0
    if tipo == "f" or tipo == "F":
        for i in range(n):
            res += a(x[i]) * b(x[i])

    elif tipo == "v" or tipo == "V":
        for i in range(n):
            res += a(x[i]) * b[i]

    return res

def g_de_x(gx, x, coeficientes, a, b):

    for i in range(int(a * 200), int(b * 200 + 1)):
        i /= 200

        x.append(i)
        gx[0].append(coeficientes[0][0] + coeficientes[0][1] * i)
        gx[1].append(coeficientes[1][0] + coeficientes[1][1] * i + coeficientes[1][2] * i ** 2)
        gx[2].append(
            coeficientes[2][0] + coeficientes[2][1] * i + coeficientes[2][2] * i ** 2 + coeficientes[2][3] * i ** 3
        )

def erro(x, y, coeficientes):

    v = np.zeros((3))

    for i in range(len(x)):

        v[0] += abs(y[i] - (coeficientes[0][0] + coeficientes[0][1] * x[i])) ** 2
        v[1] += abs(y[i] - (coeficientes[1][0] + coeficientes[1][1] * x[i] + coeficientes[1][2] * (x[i] ** 2))) ** 2
        v[2] += (
            abs(
                y[i]
                - (
                    coeficientes[2][0]
                    + coeficientes[2][1] * x[i]
                    + coeficientes[2][2] * (x[i] ** 2)
                    + coeficientes[2][3] * (x[i] ** 3)
                )
            )
            ** 2
        )

    return v

def grafico4(x, y, xg, g):
    mpl.figure()
    mpl.plot(xg, g[0], label="Aproximação com grau 1", alpha=0.7, color="#eda405")
    mpl.plot(xg, g[1], label="Aproximação com grau 2", alpha=0.7, color="#e30000")
    mpl.plot(xg, g[2], label="Aproximação com grau 3", alpha=0.7, color="#9900ff")
    mpl.plot(x, y, "bo", label="Dados originais", alpha=0.5)
    mpl.xlabel("x")
    mpl.ylabel("y/g1(x)/g2(x)/g3(x)")
    mpl.title("Minimos Quadrados")
    mpl.legend()
    mpl.grid(True)

# -------------------------------------------------------------------------------------------------#

# -----------------------------------|Dados a serem aproximados|-----------------------------------#
x = [1.0, 1.1, 1.3, 1.5, 1.9, 2.1]
y = [1.84, 1.96, 2.21, 2.45, 2.94, 3.18]
# -------------------------------------------------------------------------------------------------#

# Variável para controlar o plotamento de gráficos
graficos = True

# ------------------------------------|Declaração de variáveis|------------------------------------#
gx = [[], [], []]
xg = []
g = [g0, g1, g2, g3]

coeficientes = [[], [], []]

minq_a = [[], [], []]
minq_a[0] = np.zeros((2, 2))
minq_a[1] = np.zeros((3, 3))
minq_a[2] = np.zeros((4, 4))

minq_b = [[], [], []]
minq_b[0] = np.zeros((2))
minq_b[1] = np.zeros((3))
minq_b[2] = np.zeros((4))
# -------------------------------------------------------------------------------------------------#

# --------------------------|Calculando as matrizes de produtos internos|--------------------------#
for i in range(3):

    for j in range(2 + i):

        for k in range(2 + i):

            minq_a[i][j][k] = produto_interno(g[j], g[k], x, len(x), "f")

        minq_b[i][j] = produto_interno(g[j], y, x, len(x), "v")

    # Resolvendo o sistema linear atual
    coeficientes[i] = np.linalg.solve(minq_a[i], minq_b[i])
# -------------------------------------------------------------------------------------------------#

# Calculando o erro cometido pelos polinomios através da variancia entre a função e os dados
v = erro(x, y, coeficientes)

# ----------------------|Printando os polinomios com seus respectivos erros|-----------------------#
print("g1(x) = %f +%fx" % (coeficientes[0][0], coeficientes[0][1]))
print("Erro =", v[0], end="\n\n")

print("g2(x) = %f +%fx %fx^2" % (coeficientes[1][0], coeficientes[1][1], coeficientes[1][2]))
print("Erro =", v[1], end="\n\n")

print("g3(x) = %f +%fx +%fx^2 %fx^3" % (coeficientes[2][0], coeficientes[2][1], coeficientes[2][2], coeficientes[2][3]))
print("Erro =", v[2], end="\n\n")
# -------------------------------------------------------------------------------------------------#

# --------------------------------------|Plotando os gráficos|-------------------------------------#
if graficos == True:

    g_de_x(gx, xg, coeficientes, 0.95, 2.15)
    grafico4(x, y, xg, gx)

    gx = [[], [], []]
    xg = []

    g_de_x(gx, xg, coeficientes, -10, 10)
    grafico4(x, y, xg, gx)

    mpl.show()
# -------------------------------------------------------------------------------------------------#

Lista-implementacao-3/1 - Minimos quadrados/minimos quadrados.py


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Nov  2 16:19:21 2020

Copyright 2020 by Hadrien Montanelli.
"""
# Standard library imports:
import numpy as np

def acf(series):
    '''Compute the sample autocorrelation function of a series.

    Input
    -----
    series : numpy.ndarray
        A time series as a Tx1 array for T data points.

    Output
    ------
    output : numpy.ndarray
        The Tx1 array of sample autocorrelation.

    Example
    -------
        import numpy as np
        from learnpy.timeseries import autocorr

        x_t = np.linspace(-1, 1, 100) + 5e-1*np.random.randn(100)
        output = autocorr(x_t)
        print(output)

    See also the 'example_autocorr' file.
    '''
    # Get the number of data points:
    T = len(series)

    # Compute the sample mean and sample variance:
    sample_mean = 1/T*sum(series)
    sample_var = 1/T*sum((series - sample_mean)**2)

    # Compute the sample autocorrelation function:
    sample_acf = np.zeros(T)
    sample_acf[0] = 1
    for h in range(1, T):
        sample_acf[h] = 1/T*sum((series[h:T] - sample_mean)
                                * (series[:T-h] - sample_mean))
        sample_acf[h] /= sample_var

    return sample_acf


import numpy as np
import pywt
import cv2
import matplotlib.pyplot as plt
import Assignment_1.add_snp_noise as snp

img = cv2.imread('image_3.png', 0)
print(img.shape)
noise_img = snp.add_snp(img, 10)

# reference : https://docs.opencv.org/3.4/d5/db5/tutorial_laplace_operator.html
ddepth = cv2.CV_16S
kernel_size = 3
denoised = cv2.GaussianBlur(img, (3, 3), 8)
dst = cv2.Laplacian(denoised, ddepth, kernel_size)
abs_dst = cv2.convertScaleAbs(dst)

temp = cv2.add(img, noise_img)
final = cv2.add(temp, abs_dst)

coeffs = pywt.dwt2(final, 'haar')

cA, (cH, cV, cD) = coeffs
d = 1, (2, 3, 4)
print(d)

thres = 3 * np.std(cA)
a = coeffs[0]
# a = pywt.threshold(coeffs[0], thres, 'soft')
b = pywt.threshold(coeffs[1][0], thres, 'soft', np.mean(cH))
c = pywt.threshold(coeffs[1][1], thres, 'soft', np.mean(cV))
d = pywt.threshold(coeffs[1][2], thres, 'soft', np.mean(cD))

coeff = a, (b, c, d)

# plt.imshow(a, cmap='gray')
# plt.show()

# print("#", len(coeffs[1][1]))
#
fig = plt.figure()

ax1 = fig.add_subplot(221)
ax1.imshow(cA, cmap='gray')
ax1.set_title('LL')

ax2 = fig.add_subplot(222)
ax2.imshow(cH, cmap='gray')
ax2.set_title('LH')

ax3 = fig.add_subplot(223)
ax3.imshow(cV, cmap='gray')
ax3.set_title('HL')

ax4 = fig.add_subplot(224)
ax4.imshow(cD, cmap='gray')
ax4.set_title('HH')

plt.tight_layout()
fig = plt.gcf()
plt.show()
#
# coeffs_2 = pywt.dwt2(cA, 'haar')
#
# cA_2, (cH_2, cV_2, cD_2) = coeffs_2

fig = plt.figure()

# ax1 = fig.add_subplot(221)
# ax1.imshow(cA_2, cmap='gray')
# ax1.set_title('LL')
#
# ax2 = fig.add_subplot(222)
# ax2.imshow(cH_2, cmap='gray')
# ax2.set_title('LH')
#
# ax3 = fig.add_subplot(223)
# ax3.imshow(cV_2, cmap='gray')
# ax3.set_title('HL')
#
# ax4 = fig.add_subplot(224)
# ax4.imshow(cD_2, cmap='gray')
# ax4.set_title('HH')

res = pywt.idwt2(coeff, 'haar')

ax1 = fig.add_subplot(121)
ax1.imshow(final, cmap='gray')
ax1.set_title('Noisy image')

ax2 = fig.add_subplot(122)
ax2.imshow(res, cmap='gray')
ax2.set_title('Result')
#
plt.tight_layout()
fig = plt.gcf()
plt.show()


#from DreamDiff import DreamDiff as ad
from DreamDiff.DreamDiff import DreamDiff as ad
import numpy as np
import matplotlib.pyplot as plt

class Optimize:
    '''
    Class containing a suite of root-finding, optimization, and
    animation tools for Newton's method, gradient descent, and
    Nesterov's accelerated gradient descent.
    '''

    def newtons_method(f, x0, epsilon=0.000001, max_iters=500):
        '''
        Implements Newton's root-finding method for scalar functions.

        INPUTS
        ======
        f: str, required
            Input function on which to perform Newton's method.
        x0: int or float, required
            Starting x-value at which to initialize the algorithm.
        epsilon: int or float, optional
            Solution accuracy threshold.
        max_iters: int, optional
            The maximum number of times to run the algorithm.

        RETURNS
        =======
        If a root is found, the algorithm returns its x-value, a list
        containing the previous points at which the derivative was
        evaluated, a list of the function's values at those points, and
        a list of the function's derivatives at those points in a 4-tuple.
        If no root is found or a zero derivative is reacahed, None is
        returned.
        '''
        # Initialize xn at the starting point
        xn = x0

        # Create a DreamDiff object to access private methods
        x = ad(1.0)

        # Parse the input function string
        f_parsed = x._parse_input(f)

        # Initialize empty lists to hold results
        xn_history = []
        yn_history = []
        der_history = []

        # Run the algorithm at most 'max_iters' times
        for i in range(max_iters):

            # Calculate values, derivatives, and store in lists
            xn_history.append(xn)
            new_f = x._evaluate_function(f_parsed, xn)
            yn = new_f.val[0]
            der = new_f.der[0]
            yn_history.append(yn)
            der_history.append(der)

            # If threshold is met, terminate the algorithm and return results
            if abs(yn) < epsilon:
                print('Found solution after {} iterations.'.format(i))
                print('Solution is: {}'.format(xn))
                return (xn, xn_history, yn_history, der_history)

            # If a zero derivative is reached, terminate the algorithm and
            # return None
            if der == 0:
                print('Reached zero derivative. No solution found.')
                return None

            # Apply the Newton's method update to xn
            xn = xn - yn/der

        # If no solution is found within 'max_iters', tell the user
        print('No solution found after max iterations.')

    def animate_newtons(f, x0, epsilon=0.000001, max_iters=500, runtime=20):
        '''
        Creates an animation of Newton's root-finding method for scalar
        functions by plotting the function and showing the tangent lines
        at which the gradient was evaluated.

        INPUTS
        ======
        f: str, required
            Input function on which to perform Newton's method.
        x0: int or float, required
            Starting x-value at which to initialize the algorithm.
        epsilon: int or float, optional
            Solution accuracy threshold.
        max_iters: int, optional
            The maximum number of times to run the algorithm.
        runtime: int, optional
            The length of time to run the animation.

        RETURNS
        =======
        A matplotlib animataion showing how Newton's method located
        a root of the input function if a root was found, otherwise
        None.
        '''
        # Get the root and history of x-values, y-values, and derivatives
        # for the input function using newtons_method
        results = Optimize.newtons_method(f, x0, epsilon, max_iters)

        # Check if newtons_method returned None
        if results == None:
            print("Newton's method did not find a root.")
            return None
        else:
            root, x_vals, y_vals, grad_vals = results[0], results[1], results[2], results[3]

            # Create a DreamDiff object to access private methods
            x = ad(1.0)

            # Create a list of x-vals searched
            t_vals = np.arange(min(x_vals)-2, max(x_vals)+2, 0.1)

            # Parse the input function string
            f_parsed = x._parse_input(f)

            # Calculate the values of the input function in the range of x-vals searched
            f_vals = [x._evaluate_function(f_parsed, t).val[0] for t in t_vals]

            # Initialize the animation
            fig = plt.figure()
            fig.canvas.draw()
            fig.suptitle("Newton's Method for f(x)="+f)

            # Run the animation for the duration of 'runtime'
            while runtime > 0:

                # Plot the animation, where each frame shows the function and
                # tangent line at which the gradient was evaluated
                for i in range(len(x_vals)):
                    grad = grad_vals[i]
                    intercept = y_vals[i] - grad*x_vals[i]
                    tangent_y = grad*t_vals + intercept
                    plt.cla()
                    plt.grid(True)
                    plt.plot(t_vals, f_vals, label='Function')
                    plt.plot(t_vals, tangent_y, '-r', label='Tangent')
                    plt.xlim(t_vals[0], t_vals[-1])
                    plt.ylim(min(f_vals), max(f_vals))
                    plt.xlabel('x')
                    plt.ylabel('f(x)')
                    plt.legend()
                    plt.pause(0.2)
                    runtime -= 1
            return None

    def grad_descent(f, x0, epsilon=0.000001, max_iters=500, eta=0.1):
        '''
        Implements the gradient descent optimization algorithm.

        INPUTS
        ======
        f: str, required
            Input function on which to perform gradient descent.
        x0: int or float, required
            Starting x-value at which to initialize the algorithm.
        epsilon: int or float, optional
            Solution accuracy threshold.
        max_iters: int, optional
            The maximum number of times to run the algorithm.
        eta: int or float, optional
            The learning rate, which controls the algorithm step size.

        RETURNS
        =======
        If a minimum is found, the algorithm returns its x-value, a list
        containing the previous points at which the derivative was
        evaluated, a list of the function's values at those points, and
        a list of the function's derivatives at those points in a 4-tuple.
        If no minimum is found, None is returned.
        '''
        # Initialize xn at the starting point
        xn = x0

        # Create a DreamDiff object to access private methods
        x = ad(1.0)

        # Parse the input function string
        f_parsed = x._parse_input(f)

        # Initialize empty lists to hold results
        xn_history = []
        yn_history = []
        der_history = []

        # Run the algorithm at most 'max_iters' times
        for i in range(max_iters):

            # Calculate values, derivatives, and store in lists
            xn_history.append(xn)
            new_f = x._evaluate_function(f_parsed, xn)
            yn = new_f.val[0]
            der = new_f.der[0]
            yn_history.append(yn)
            der_history.append(der)

            # Apply the gradient descent update (scaled by learning rate)
            new_xn = xn - eta*der

            # If threshold is met, terminate the algorithm and return results
            if abs(new_xn - xn) < epsilon:
                print('Found solution after {} iterations.'.format(i))
                print('Solution is: {}'.format(new_xn))
                return (new_xn, xn_history, yn_history, der_history)

            # Update xn
            xn = new_xn

        # If no solution is found within 'max_iters', tell the user and return None
        print('No solution found after max iterations.')
        return None

    def nesterov_grad_descent(f, x0, epsilon, max_iters, eta):
        '''
        Implements Nesterov's accelerated gradient descent
        optimization algorithm, which uses a momentum parameter 't'.

        INPUTS
        ======
        f: str, required
            Input function on which to perform gradient descent.
        x0: int or float, required
            Starting x-value at which to initialize the algorithm.
        epsilon: int or float, optional
            Solution accuracy threshold.
        max_iters: int, optional
            The maximum number of times to run the algorithm.
        eta: int or float, optional
            The learning rate, which controls the algorithm step size.

        RETURNS
        =======
        If a minimum is found, the algorithm returns its x-value, a list
        containing the previous points at which the derivative was
        evaluated, a list of the function's values at those points, and
        a list of the function's derivatives at those points in a 4-tuple.
        If no minimum is found, None is returned.
        '''
        # Initialize xn at the starting point
        xn = x0

        # Create a DreamDiff object to access private methods
        x = ad(1.0)

        # Initialize the momentum coefficient
        t = 1.0

        # Initialize the y-value
        y = x0

        # Parse the input function string
        f_parsed = x._parse_input(f)

        # Initialize empty lists to hold results
        xn_history = []
        yn_history = []
        der_history = []

        # Run the algorithm at most 'max_iters' times
        for i in range(max_iters):

            # Calculate values, derivatives, and store in lists
            xn_history.append(xn)
            new_f = x._evaluate_function(f_parsed, y)
            yn = new_f.val[0]
            der = new_f.der[0]
            yn_history.append(yn)
            der_history.append(der)

            # Apply Nesterov's momentum update
            new_t = 0.5*(1 + np.sqrt(1 + 4*t**2))

            # Calculate the new gradient (scaled by learning rate)
            new_xn = y - eta*der
            new_y = new_xn + (t - 1.0)/(new_t)*(new_xn - xn)

            # If threshold is met, terminate the algorithm and return results
            if abs(new_xn - xn) < epsilon:
                print('Found solution after {} iterations.'.format(i))
                print('Solution is: {}'.format(new_xn))
                return (new_xn, xn_history, yn_history, der_history)

            # Reset conditions
            if np.dot(y - new_xn, new_xn - xn) > 0:
                new_y = new_xn
                new_t = 1

            # Update xn, y, t
            xn = new_xn
            y = new_y
            t = new_t

        # If no solution is found within 'max_iters', tell the user and return None
        print('No solution found after max iterations.')
        return None

    def animate_grad_desc(f, x0, epsilon=0.000001, max_iters=500, eta=0.1, method='grad', runtime=20):
        '''
        Creates an animation of the gradient descent method for scalar
        functions by plotting the function and showing the tangent lines
        at which the gradient was evaluated.

        INPUTS
        ======
        f: str, required
            Input function on which to perform gradient descent.
        x0: int or float, required
            Starting x-value at which to initialize the algorithm.
        epsilon: int or float, optional
            Solution accuracy threshold.
        max_iters: int, optional
            The maximum number of times to run the algorithm.
        eta: int or float, optional
            The learning rate, which controls the algorithm step size.
        method: str, optional
            The gradient descent method to use: input 'grad' to use
            standard gradient descent and 'nesterov' to use
            Nesterov's accelerated gradient descent.
        runtime: int, optional
            The length of time to run the animation.

        RETURNS
        =======
        A matplotlib animataion showing how gradient descent located
        a minimum of the input function, depending on the descent method
        used.
        '''
        # Get the minimum and history of x-values, y-values, and derivatives
        # for the input function using grad_descent
        if method == 'grad':
            results = Optimize.grad_descent(f, x0, epsilon, max_iters, eta)
        elif method == 'nesterov':
            results = Optimize.nesterov_grad_descent(f, x0, epsilon, max_iters, eta)
        else:
            raise Exception(f'invalid gradient descent method: {method}')

        # Check if grad_descent returned None
        if results == None:
            print('Gradient descent did not find a minimum.')
            return None
        else:
            minimum, x_vals, y_vals, grad_vals = results[0], results[1], results[2], results[3]

            # Create a DreamDiff object to access private methods
            x = ad(1.0)

            # Create a list of x-vals searched
            t_vals = np.arange(min(x_vals)-2, max(x_vals)+2, 0.1)

            # Parse the input function string
            f_parsed = x._parse_input(f)

            # Calculate the values of the input function in the range of x-vals searched
            f_vals = [x._evaluate_function(f_parsed, t).val[0] for t in t_vals]

            # Initialize the animation
            fig = plt.figure()
            fig.canvas.draw()
            fig.suptitle("Gradient Descent for f(x)="+f)

            # Run the animation for the duration of 'runtime'
            while runtime > 0:

                # Plot the animation, where each frame shows the function and
                # tangent line at which the gradient was evaluated
                for i in range(len(x_vals)):
                    grad = grad_vals[i]
                    intercept = y_vals[i] - grad*x_vals[i]
                    tangent_y = grad*t_vals + intercept
                    plt.cla()
                    plt.grid(True)
                    plt.plot(t_vals, f_vals, label='Function')
                    plt.plot(t_vals, tangent_y, '-r', label='Tangent')
                    plt.xlim(t_vals[0], t_vals[-1])
                    plt.ylim(min(f_vals), max(f_vals))
                    plt.xlabel('x')
                    plt.ylabel('f(x)')
                    plt.legend()
                    plt.pause(0.2)
                    runtime -= 1
            return None

    def plot_coeffs(coeffs, x_array, y_array):
        '''
        Plots coefficients along with original points

        INPUTS
        ======
        coeffs: array of length 3n, required
            The x values for user's given points, must be same length as y_array

        x_array: array of numeric values of length n+1, required
            The x values for user's given points, must be same length as y_array
        y_array: array of numeric values of length n+1, required
            The y values for user's given points, must be same length as x_array

        RETURNS
        =======
        True if plotted, nothing otherwise.
        '''
        # check x and y for all points
        if not (len(x_array) == len(y_array)):
            raise Exception('Number of x values for data points do not match number of y values')

        num_eq = int(len(coeffs)/3)

        # plot each equation
        for i in range(num_eq):
            # define quadratic function for plotting
            def f(x, coeffs, i):
                return coeffs[i*3]*(x**2) + coeffs[(i*3)+1]*x + coeffs[(i*3)+2]

            temp_x = []
            # plot individual quadratic function
            temp_arange = np.arange(start = x_array[i], stop=x_array[i+1], step=0.01)
            for j in temp_arange:
                temp_x.append(f(j, coeffs, i))
            plt.plot(temp_arange, temp_x)

        # plot points
        plt.plot(x_array, y_array, 'ro')
        plt.title('Quadratic Splines')
        plt.show()
        return True

    def quadratic_spline(x_array, y_array, plot = False):
        '''
        Runs Quadratic Spline interpolation on a set of points.

        INPUTS
        ======
        x_array: array of numeric values, required
            The x values for user's given points, must be same length as y_array
        y_array: array of numeric values, required
            The x values for user's given points, must be same length as x_array
        plot: bool, optional, default = False
            If plot is True, plots the piecewise quadratic splines using matplotlib.pyplot

        RETURNS
        =======
        Coeffs: list of floats
            The list of coefficients to the quadratic spline equations in list as [a_1, b_1, c_1,... a_n, b_n, c_n]

        Prints piecewise function using coefficients.

        If plot==True, displays quadratic splines using matplotlib.pyplot
        '''

        # ensure all points have x and y values
        if not (len(x_array) == len(y_array)):
            raise Exception('number of x values for data points do not match number of y values')

        # ensure at least 3 data points for quadratic splines
        if len(x_array)<3:
            raise Exception('Too few data points for quadratic spline interpolation')

        n = len(x_array) - 1
        sys_of_equations = np.zeros((3*n, 3*n))

        # define AutoDiff objects a,b,c
        def a(ad_val):
            return ad(ad_val**2)
        def b(ad_val):
            return ad(ad_val)
        def c(ad_val):
            return ad(1)

        # define 3n linear equations
        for i in range(n):
            # Equations to connect quadratic between point i, i+1
            sys_of_equations[3*i][(3*i)] = a(x_array[i]).val
            sys_of_equations[3*i][((3*i)+1)] = b(x_array[i]).val
            sys_of_equations[3*i][((3*i)+2)] = c(x_array[i]).val
            sys_of_equations[(3*i)+ 1][(3*i)] = a(x_array[i+1]).val
            sys_of_equations[(3*i)+ 1][((3*i)+1)] = b(x_array[i+1]).val
            sys_of_equations[(3*i)+ 1][((3*i)+2)] = c(x_array[i+1]).val

            if i!=n-1:
                # Constraints so derivative matches at all interior points
                sys_of_equations[(3*i)+ 2][(3*i)] = a(x_array[i+1]).der
                sys_of_equations[(3*i)+ 2][((3*i)+1)] = b(x_array[i+1]).der
                sys_of_equations[(3*i)+ 2][((3*i)+2)] = c(x_array[i+1]).der
                sys_of_equations[(3*i)+ 2][(3*(i+1))] = -(a(x_array[i+1]).der)
                sys_of_equations[(3*i)+ 2][((3*(i+1))+1)] = -(b(x_array[i+1]).der)
                sys_of_equations[(3*i)+ 2][((3*(i+1))+2)] = -(c(x_array[i+1]).der)

        # Last of 3n equations
        sys_of_equations[3*n -1][0] = 1

        # construct y array for linear equations
        y = np.zeros(3*n)
        for i in range(n):
            y[(3*i)] = y_array[i]
            y[(3*i)+1] = y_array[i+1]
            y[(3*i)+2] = 0

        # solve for coeffs
        coeffs_all = np.linalg.lstsq(sys_of_equations, y, rcond=None)
        coeffs = coeffs_all[0]

        # truncate coeffs
        for i in range(len(coeffs)):
            coeffs[i] = float(f"{coeffs[i]:.4f}")

        print("Quadratic Spline Equations: ")
        for index in range(n):
            print("S_{index}(x) = {a}*x^2 + {b}*x + {c} for x ∈ [{i_0}, {i_1}]".format(
                index=index, a=coeffs[index*3], b=coeffs[index*3+1], c=coeffs[index*3+2], i_0=x_array[index], i_1=x_array[index+1]
                ))
        # plot
        if plot == True:
            spline_plot = Optimize.plot_coeffs(coeffs, x_array, y_array)

        return coeffs