import numpy as np
import scipy.linalg as spl

class Sequency():

    def __init__(self):
        pass

    def _get_sequency(self,input_array):

        input_shift = np.roll(input_array,1)
        input_shift[0]= 0
        diffs = input_array + input_shift
        return np.sum(np.where(diffs==0,1,0))

    def walsh_matrix(self,scale):
        """
        Generate sequency order Walsh functions and return in a matrix.

        Parameters
        ==============
        scale : int
            The length of the Walsh functions, must be a power of 2, else will raise an exception.

            .. note::
                A scale = 8 means each Walsh function will have 8 data points and therefore be 8 units in length.

        Returns
        ============
        tuple(array, array(scale x scale))
            sequencies, scale x scale matrix of Walsh functions

        """

        hadamard = spl.hadamard(scale,dtype=int)
        sequencies = np.apply_along_axis(self._get_sequency, 1, hadamard)
        return np.sort(sequencies),hadamard[np.argsort(sequencies)]

    def fwt(self,input_values,basis_span='post',pad_mode='constant', **kargs):
        """
        Perform a Fast Discrete Walsh Transform on the input data. This will decompose the data using Walsh basis functions and create a vector of
        weighting values that represent the amplitude of each Walsh basis within the data. This is the discrete step decomposition equivalent of
        a Discrete Fast Fourier Transform, whereby the Sine and Cosine basis functions are replaced by Walsh functions.

        Parameters
        =============
        input_values : array_like
            The data to be decomposed via Walsh functions.

        basis_span : {'post','pre'}, Optional
            How long the Walsh basis functions should be relative to the input data.
             - **post** will extend the Walsh basis to the next length beyond the input data length that is a power of 2.
             - **pre** will truncate the Walsh basis to the next length less than the input data length that is a power of 2.

        pad_mode : {None, 'edge','constant'}, Optional
            This is how the input data is to be padded to match the requirement of each Walsh basis being a length which is a power of 2.

            .. note::
                The allowable parameters are the same as those for the Numpy.pad function and therefore any valid mode string or function
                can be used here and it will be passed to the Numpy.pad function.

        kargs : any
            Key-value arguments that will be passed to the Numpy.pad function, please see the Numpy.pad documentation for further details of
            allowable kargs for this function.

        Returns
        ==========
        tuple(array, array, int)
            sequencies, spectrum_amplitudes, walsh function range

        See Also
        ==========
        Sequency.fft

        References
        ==========
        .. https://mathworld.wolfram.com/WalshTransform.html

        """

        l = len(input_values)

        if basis_span == 'post':
            scale = 2**(1 + int(np.trunc(np.log2(l))))
        else:
            scale = 2**(int(np.trunc(np.log2(l))))

        if l % scale != 0 and scale > l:
            input_scaled = np.pad(input_values,(0,scale - l),pad_mode, **kargs)
        else:
            input_scaled = input_values[:scale]

        sf, wm = self.walsh_matrix(scale)
        wt = np.dot(wm,input_scaled)/scale

        return sf, wt,scale

    def ifwt(self,fwt_data, scale=None, return_length=None):
        """
        Perform an inverse Fast Discrete Walsh Transform on the input data. The input data is expected to be an array representing the transform amplitudes from a Walsh transform of a dataset.
        This method will use the amplitude data to reconstruct the original dataset using the provided ammplitudes matrix. The scale of the Walsh basis functions can be specified explcitly using the **scale** parameter, else if
        this parameter is not explicitly set, the scale value will be derived from the length of the fwt_data (amplitude) array.

        Parameters
        ==============
        fwt_data : array_like
            The Walsh transform amplitudes to use to reconstruct the dataset.

        scale : int, Optional
            The scale (length) of the Walsh functions to use when performing the inverse Walsh transform.

            .. note::
                This value must be a multiple of 2 or an exception will be raised.

        return_length : int, Optional
            The length of the reconstructed dataset, as the length of the original data may not be a multiple of 2, the scale of the Walsh functions used in thr transform (fwt) method may be shorter or longer, therefore when returning a reconstruction, you can specify a length to truncate the reconstuction at.

        Returns
        ==========
        array
            reconstructed dataset

        References
        =============
        .. https://mathworld.wolfram.com/WalshTransform.html

        """

        if scale is None:
            scale = len(fwt_data)

        _, wm = self.walsh_matrix(scale)
        iwt = np.dot(wm,fwt_data[:scale])

        if return_length is None:
            return iwt
        else:
            return iwt[:return_length]

    def denoise(self,input_values,method='walsh', basis_span='post',pad_mode='constant', denoise_mode='value',denoise_strength=0.5):
        """
        Perform a denoise operation on the input_values using either Walsh or Fourier method.

        Parameters
        ============
        input_values : array_like
            The data to be denoised via Walsh transform.

        method : {'walsh', 'fourier}, Optional
            The denoise method to apply to the data.
             - **walsh**, use a Walsh decomposition to produce the components to modify for denoising.
             - **fourier**, use a Fourier decomposition to produce the components to modify for denoising.

        basis_span : {'post','pre'}, Optional
            How long the Walsh basis functions should be relative to the input data.
             - **post** will extend the Walsh basis to the next length beyond the input data length that is a power of 2.
             - **pre** will truncate the Walsh basis to the next length less than the input data length that is a power of 2.

        pad_mode : {None, 'edge','constant'}, Optional
            This is how the input data is to be padded to match the requirement of each Walsh basis being a length which is a power of 2.

            .. note::
                The allowable parameters are the same as those for the Numpy.pad function and therefore any valid mode string or function
                can be used here and it will be passed to the Numpy.pad function.

        denoise_mode : {'range', 'value'}, Optional
            The type of filtering to use.
             - **range** will remove a proportion of the higher sequency/frequency components based on the denoise_strength value.
             - **value** will remove the sequency/frequency components that are below the value based on the denoise_strength.

        denoise_strength : float, Optional
            The strength of the denoising, this parameter controls how the components are altered in order to denoise the input_data.

        Returns
        =============
        array
            denoised data

        """

        if method == 'walsh':
            return self._walsh_denoise(input_values=input_values,basis_span=basis_span,pad_mode=pad_mode, denoise_mode=denoise_mode,denoise_strength=denoise_strength)
        else:
            return self._fourier_denoise(input_values=input_values,denoise_mode=denoise_mode,denoise_strength=denoise_strength)

    def _walsh_denoise(self,input_values,basis_span='post',pad_mode='constant', denoise_mode='value',denoise_strength=0.5):
        """
        Perform a Fast Discrete Walsh Transform on the input data, selectively removing sequency components by setting their amplitudes to zero, then reconstruct the original dataset by performing an inverse Walsh transform on the remaining amplitudes.

        Parameters
        ============
        input_values : array_like
            The data to be denoised via Walsh transform.

        basis_span : {'post','pre'}, Optional
            How long the Walsh basis functions should be relative to the input data.
             - **post** will extend the Walsh basis to the next length beyond the input data length that is a power of 2.
             - **pre** will truncate the Walsh basis to the next length less than the input data length that is a power of 2.

        pad_mode : {None, 'edge','constant'}, Optional
            This is how the input data is to be padded to match the requirement of each Walsh basis being a length which is a power of 2.

            .. note::
                The allowable parameters are the same as those for the Numpy.pad function and therefore any valid mode string or function
                can be used here and it will be passed to the Numpy.pad function.

        denoise_mode : {'range', 'value'}, Optional
            The type of filtering to use.
             - **range** will remove a proportion of the higher sequency components based on the denoise_strength value.
             - **value** will remove the sequency components that are below the value based on the denoise_strength.

        denoise_strength : float, Optional
            The strength of the denoising, this parameter controls how the components are altered in order to denoise the input_data.

        Returns
        =============
        array
            denoised data

        """

        l = len(input_values)

        _,wt,sc = self.fwt(input_values=input_values,basis_span=basis_span,pad_mode=pad_mode)

        if denoise_strength >= 0 and denoise_strength < 1:
            if denoise_mode == 'range':
                filter_idx = int(denoise_strength*sc)
                wt[-filter_idx:] = 0
            else:
                denoise_threshold = np.amax(np.absolute(wt))*denoise_strength
                wt = np.where(np.absolute(wt)>denoise_threshold, wt, 0)

        return self.ifwt(wt,scale=sc,return_length=l)

    def _fourier_denoise(self,input_values, denoise_mode='value',denoise_strength=0.5):
        """
        Perform a Fast Discrete Fourier Transform on the input data, selectively removing frequency components by setting their amplitudes to zero, then reconstruct the original dataset by performing an inverse Fourier transform on the remaining amplitudes.

        Parameters
        ============
        input_values : array_like
            The data to be denoised via Walsh transform.

        denoise_mode : {'range', 'value'}, Optional
            The type of filtering to use.
             - **range** will remove a proportion of the higher frequency components based on the denoise_strength value.
             - **value** will remove the frequency components that are below the value based on the denoise_strength.

        denoise_strength : float, Optional
            The strength of the denoising, this parameter controls how the components are altered in order to denoise the input_data.

        Returns
        =============
        array
            denoised data

        """

        l = len(input_values)

        _,ft = self.fft(input_values)

        if denoise_strength >= 0 and denoise_strength < 1:
            if denoise_mode == 'range':
                    filter_idx = int(denoise_strength*len(ft))
                    ft[-filter_idx:] = 0
            else:
                denoise_threshold = np.amax(np.absolute(ft))*denoise_strength
                ft = np.where(np.absolute(ft)>denoise_threshold, ft, 0)

        return self.ifft(ft)

    def sequency_spectrum(self, input_values,pad_mode='constant',**kargs):
        """
        A convenient Sequency power spectrum representation of the input_values data, this will be normalised by the sum of squares of all amplitude values, thus representing a proportional power spectrum.

        Parameters
        ===========
        input_values : array_like
            The data (best to use step change values) to be analysed to generate a sequency power spectrum.

        pad_mode : {None, 'edge','constant'}, Optional
            This is how the input data is to be padded to match the requirement of each Walsh basis being a length which is a power of 2.

            .. note::
                The allowable parameters are the same as those for the Numpy.pad function and therefore any valid mode string or function
                can be used here and it will be passed to the Numpy.pad function.

        kargs : any
            Key-value arguments that will be passed to the Numpy.pad function, please see the Numpy.pad documentation for further details of
            allowable kargs for this function.

        Returns
        =============
        tuple(array, array, int)
            sequencies, spectrum_amplitudes, walsh function range

        """

        sf,wm,sc = self.fwt(input_values,pad_mode=pad_mode,**kargs)
        return sf, np.power(wm,2)/(np.sum(np.power(wm,2))),sc

    def frequency_spectrum(self,data,sampling_frequency=1):
        """
        A basic implementation of the Fast Fourier Transform power spectrum as implemented in Numpy.fft.fft.

        Parameters
        =============
        data : array_like
            The data that the frequency spectrum is generated from.

        sampling_frequency : int, Optional
            The sampling frequency used to scale the frequency spectrum, specifically this will need to be tuned based on your data as different scalings will be required to suit the type and density of data points.

        Returns
        ===========
        tuple(array, array)
            frequencies, spectrum_amplitudes

        See Also
        ===========
        Sequency.sequency_spectrum

        References
        =================
        .. https://numpy.org/doc/stable/reference/routines.fft.html

        """

        fourierTransform = np.fft.fft(data)/len(data)
        fourierTransform = fourierTransform[range(int(len(data)/2))]

        tpCount=len(data)
        values=np.arange(int(tpCount/2))
        timePeriod=tpCount/sampling_frequency
        frequencies=values/timePeriod

        return frequencies, np.absolute(fourierTransform)/(np.sum(np.absolute(fourierTransform)))

    def fft(self,data,sampling_frequency=1):
        """
        A basic implementation of the Fast Fourier Transform as implemented in Numpy.fft.fft.

        Parameters
        =============
        data : array_like
            The data that is to be transformed using the fft method from Numpy.

        sampling_frequency : int, Optional
            The sampling frequency used to scale the frequency spectrum, specifically this will need to be tuned based on your data as different scalings will be required to suit the type and density of data points.

        Returns
        ===========
        tuple(array, array)
            frequencies, transform_amplitudes

        See Also
        ===========
        Sequency.sequency_spectrum

        References
        =================
        .. https://numpy.org/doc/stable/reference/routines.fft.html

        """

        fourierTransform = np.fft.fft(data)
        #fourierTransform = fourierTransform[range(int(len(data)/2))]

        tpCount=len(data)
        values=np.arange(int(tpCount/2))
        timePeriod=tpCount/sampling_frequency
        frequencies=values/timePeriod

        return frequencies, fourierTransform

    def ifft(self,data):
        """
        A basic implementation of the inverse Fast Fourier Transform as implemented in Numpy.fft.ifft.

        Parameters
        =============
        data : array_like
            The Fourier transform data that is to be inverted back into the time domain.

        Returns
        ===========
        array
            data

        See Also
        ===========
        Sequency.ifwt

        References
        =================
        .. https://numpy.org/doc/stable/reference/routines.ifft.html

        """

        ifourierTransform = np.fft.ifft(data)

        return ifourierTransform


import numpy as np
import math

import matplotlib.pyplot as plt

def volume_ellipsoid(Q):
    """
    volume of set x.T @ Q^{-1} @ x <= 1

    see https://faculty.fuqua.duke.edu/~psun/bio/MVEpaper_accept.pdf
    or
        Grötschel, M., L. Lovasz, A. Schrijver. 1998.
        Geometric Algorithms and Combinatorial Optimization.
        Springer-Verlag, Berlin, Germany.
    """
    n = Q.shape[0]
    m_pi, gamma = math.pi, math.gamma(n/2. + 1)
    det_Qinv = 1. / np.linalg.det(Q)
    vol = (m_pi**(n / 2.) / gamma) * (1./np.sqrt(det_Qinv))
    return vol

def is_in_ellipse(x, mu, Q):
#     return (x-mu).T @ np.linalg.inv(Q) @ (x-mu) < 1.
    return (x-mu).T @ np.linalg.solve(Q, (x-mu)) < 1.

def count_nb_in_ellipse(Xs, mu, Q):
    if mu.size != Q.shape[0] or Xs.shape[1] != mu.size:
        raise ValueError("Xs (%d,%d), mu (%d) and Q (%d,%d) must be the same size" %(Xs.shape[0], Xs.shape[1], mu.size, Q.shape[0], Q.shape[1]))

    nb_in_ellipse = 0.
    for xi in Xs:
        if is_in_ellipse(xi, mu, Q) == True:
            nb_in_ellipse += 1
    return nb_in_ellipse

def count_nb_in_ellipse_and_get_idx_inside(Xs, mu, Q):
    if mu.size != Q.shape[0] or Xs.shape[1] != mu.size:
        raise ValueError("Xs (%d,%d), mu (%d) and Q (%d,%d) must be the same size" %(Xs.shape[0], Xs.shape[1], mu.size, Q.shape[0], Q.shape[1]))

    nb_in_ellipse = 0.
    indices_inside = []
    for idx,xi in enumerate(Xs):
        if is_in_ellipse(xi, mu, Q) == True:
            indices_inside.append(idx)
            nb_in_ellipse += 1
    return nb_in_ellipse, indices_inside

def percentage_in_ellipse(Xs, mu, Q):
    if mu.size != Q.shape[0] or Xs.shape[1] != mu.size:
        raise ValueError("Xs (%d,%d), mu (%d) and Q (%d,%d) must be the same size" %(Xs.shape[0], Xs.shape[1], mu.size, Q.shape[0], Q.shape[1]))

    return count_nb_in_ellipse(Xs, mu, Q) / Xs.shape[0]

def sample_pts_ellipsoid_surface(mu, Q, NB_pts, random=True):
    """
    Uniformly samples points on the surface of an ellipsoid, specified as
    (xi-mu)^T Q^{-1} (xi-mu) == 1
    arguments: mu      - mean [dim]
                Q      - Q [dim x dim]
                NB_pts - nb of points
                random - True: Uniform sampling.
                         False: Uniform deterministic grid
    output:    ell_pts - points on the boundary of the ellipse [xdim x NB_pts]
    """
    dim = mu.shape[0]
    if dim != Q.shape[0] or dim != Q.shape[1]:
        raise ValueError("mu (%d) and Q (%d,%d) must be the same size" %(mu.shape[0], Q.shape[0], Q.shape[1]))
    if (Q == np.zeros((dim,dim))).all():
        return np.zeros((dim,NB_pts))

    if random == False and dim > 2:
        raise ValueError("sample_pts_ellipsoid_surface: non random sampling not implemented")

    mut = np.array([mu])
    pts = sample_pts_unit_sphere(dim, NB_pts, random=random).T
    E   = np.linalg.cholesky(Q)

    ell_pts = (mut + pts @ E.T).T

    return ell_pts

def sample_pts_unit_sphere(dim, NB_pts, random=True):
    """
    Uniformly samples points on a d-dimensional sphere (boundary of a ball)
    Points characterized by    ||x||_2 = 1
    arguments:  dim    - nb of dimensions
                NB_pts - nb of points
                random - True: Uniform sampling.
                         False: Uniform deterministic grid
    output:     pts    - points on the boundary of the sphere [xdim x NB_pts]
    Reference: http://extremelearning.com.au/how-to-generate-uniformly-random-points-on-n-spheres-and-n-balls/
    """
    if dim == 2 and random == False:
        angles = np.linspace(0., 2*np.pi, num=NB_pts, endpoint=False)
        pts = np.array([np.cos(angles),np.sin(angles)])
        return pts

    if random == False and dim > 2:
        raise ValueError("sample_pts_unit_sphere: non random sampling not implemented")

    u = np.random.normal(0, 1, (dim,NB_pts))
    d = np.sum(u**2,axis=0) **(0.5)
    pts = u/d
    return pts

def sample_pts_unit_ball(dim, NB_pts):
    """
    Uniformly samples points in a d-dimensional sphere (in a ball)
    Points characterized by    ||x||_2 < 1
    arguments:  dim    - nb of dimensions
                NB_pts - nb of points
    output:     pts    - points sampled uniformly in ball [xdim x NB_pts]
    Reference: http://extremelearning.com.au/how-to-generate-uniformly-random-points-on-n-spheres-and-n-balls/
    """

    us    = np.random.normal(0,1,(dim,NB_pts))
    norms = np.linalg.norm(us, 2, axis=0)
    rs    = np.random.random(NB_pts)**(1.0/dim)
    pts   = rs*us / norms

    return pts

def sample_pts_in_ellipsoid(mu, Q, NB_pts):
    """
    Uniformly samples points in an ellipsoid, specified as
            (xi-mu)^T Q^{-1} (xi-mu) <= 1
    arguments: mu - mean [dim]
                Q - Q [dim x dim]
    output:     pts - points sampled uniformly in ellipsoid [xdim x NB_pts]
    """
    xs = sample_pts_unit_ball(mu.shape[0], NB_pts)

    E  = np.linalg.cholesky(Q)
    ys = (np.array(E@xs).T + mu).T

    return ys

def sample_pts_in_hyperrectangle(center, widths, NB_pts_each_dim, random=False):
    """
    arguments:  center          - (dim,  ) center
                widths          - (dim,  ) total widths from and to edges of rectangle
                NB_pts_each_dim - number of points used for sampling along each dimension
                random          - deterministic or random (uniform) sampling (not implemented yet)
    output:     uniformly sampled dim-dimensional points in the rectangle:
                        |xi - center| < width/2
                    size: [xdim x NB_total_points)]
    """
    if random==True:
        print('[sample_pts_in_hyperrectangle] non deterministic (meshgrid) sampling not implemented.')

    dim = center.shape[0]
    M   = NB_pts_each_dim

    edges = np.array([center-widths/2, center+widths/2]).T

    out = np.mgrid[[np.s_[edges[d,0]:edges[d,1]:(M*1j)] for d in range(dim)]]
    out = out.T.reshape(-1,dim).T
    return out

def get_hyperrectangle_volume(widths):
    return np.prod(widths)

def test_ellipsoid_function(NB_pts=100):

    mu = np.array([1,2])
    Q = np.array([[1 ,0.2], [0.2 ,1.6]])

    pts = sample_pts_ellipsoid_surface(mu, Q, NB_pts)

    plt.scatter(pts[0,:], pts[1,:])

def bound_rectangle_with_ellipsoid(widths):
    dim = widths.shape[0]
    Q = dim * np.diag(widths)
    return Q


# Copyright 2018 - Jonathan Alcantara e Osmar Fernandes

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from math import pow
from math import sqrt

def cholesky(A):
    # if not np.all(np.linalg.eigvals(A) > 0):
    #     print "Matriz nao e Positiva Definida"
    #     return false

    L = [[0.0] * len(A) for i in range(len(A))]

    for row in range(len(A)):
        sum_row = 0
        for k in range(row):
            sum_row += pow(L[row][k], 2)

        L[row][row] = sqrt(A[row][row] - sum_row)
        print('%s, %s: %s' % (row+1, row+1, L[row][row]))
        for column in range(row + 1, len(A)):
            sum_column = 0
            for k in range(row):
                sum_column += L[row][k]*L[column][k]

            L[column][row] = (A[row][column] - sum_column)/L[row][row]
            print('%s, %s: %s' % (row+1, column+1, L[column][row]))

    A = np.array(A)
    L = np.array(L)
    transposed_L = np.transpose(L)

    print '\nA : \n', A
    print '\nL : \n', L
    print '\nL_T :\n', transposed_L

    return A, L, transposed_L

def cholesky_solution(L, B):
    print 'Cholesky Solution: ', np.matmul(np.linalg.inv(np.matmul(L, np.transpose(L))), np.transpose(B))

def cholesky_det(L):
    print 'Cholesky Determinant: ',  np.power(np.prod(L.diagonal()), 2)


'''
03 - Efficient summaries

While pandas and NumPy have tons of functions, sometimes, you may need a different
function to summarize your data.

The .agg() method allows you to apply your own custom functions to a DataFrame,
as well as apply functions to more than one column of a DataFrame at once, making
your aggregations super-efficient.

For example:

df['column'].agg(function)

In the custom function for this exercise,"IQR" is short for inter-quartile range,
which is the 75th percentile minus the 25th percentile. It's an alternative to
standard deviation that is helpful if your data contains outliers.

Instructions:

1. Use the custom iqr function defined for you along with .agg() to print the IQR
   of the temperature_c column of sales.

2. Update the column selection to use the custom iqr function with .agg() to print
   the IQR of temperature_c, fuel_price_usd_per_l, and unemployment, in that order.

3. Update the aggregation functions called by .agg(): include iqr and np.median
   in that order.
 ---------------------------------------------------------------------------------------------------------------
sales.head()

store type  department          date  weekly_sales  is_holiday  temperature_c  fuel_price_usd_per_l  unemployment
0      1    A           1 2010-02-05      24924.50       False          5.728                 0.679         8.106
1      1    A           1 2010-03-05      21827.90       False          8.056                 0.693         8.106
2      1    A           1 2010-04-02      57258.43       False         16.817                 0.718         7.808
3      1    A           1 2010-05-07      17413.94       False         22.528                 0.749         7.808
4      1    A           1 2010-06-04      17558.09       False         27.050                 0.715         7.808
 ----------------------------------------------------------------------------------------------------------------
'''

# Import pandas and numpy
import numpy as np
import pandas as pd

sales = pd.read_csv('content/wallmart_sales.csv')

# A custom IQR function
def iqr(column):
    return column.quantile(0.75) - column.quantile(0.25)

# Print IQR of the temperature_c column
print(sales['temperature_c'].agg(iqr))

# Update to print IQR of temperature_c, fuel_price_usd_per_l, & unemployment
print(sales[["temperature_c", 'fuel_price_usd_per_l', 'unemployment']].agg(iqr))

# Update to print IQR and median of temperature_c, fuel_price_usd_per_l, & unemployment
print(sales[["temperature_c", "fuel_price_usd_per_l", "unemployment"]].agg([iqr, np.median]))

04_Data Manipulation with pandas/02_Aggregating Data/03_Efficient summaries.py


import numpy as np
import numpy.linalg as la
import matplotlib.pyplot as plt
import pandas as ps

# Determinant
A = np.array([[0,-1],[1,0]])
print("\n Determinant of 2x2: ",round(la.det(A)))

B = np.array([[2,4,3],[3,5,2],[5,4,2]])
print("\n Determinant of 3x3 matrix: ",round(la.det(B)))

print("\n Matrix: ")
print(A)
print("\n Inverse of A: ")
print(la.inv(A))
print("\n Transpose: ")
print(A.T)

#Eigen Values and Eigen Vectors
C= np.array([[2,4],[6,7]])
eigenvals = la.eigvals(C)
eigval, eigvec = la.eig(C)

print("\n Original matrix: ")
print(C)
print("\n Just the Eigen values: ")
print(eigenvals)
print("\n Eigen Values: ")
print(eigval)
print("\n Eigen vectors: ")
print(eigvec)

D=np.array([[1,2,3,4],[2,2,2,2],[4,3,2,5],[3,2,4,3]])
eigenValss = la.eigvals(D)
print("\n Original 4x4 Matrix: ")
print(D)
print("\n Eigen values of a 4x4 matrix: ")
print(eigenValss)

F = np.array([[1,4],[4,1]])
eigval, eigvec = la.eig(A)

v1=eigvec[:,0]
v2=eigvec[:,1]

Av1 = F @ v1
Av2 = F @ v2

v1_mag = np.sqrt(np.sum(np.square(eigvec[:,0])))
v2_mag = np.sqrt(np.sum(np.square(eigvec[:,1])))

print("Original Matrix: ")
print(F)
print("\n The Eigen Decomposition")
print("The Eigen values: ")
print(eigval)
print("The Eigen vectors: ")
print(eigvec)
print("\n The Magnitude of v1 and v2: ")
print(round(v1_mag), round(v2_mag))

plt.plot([0, v1[0]], [0, v1[1]], 'black', label='v1', linewidth=2)
plt.plot([0, v2[0]], [0, v2[1]], 'yellow',label='v2', linewidth=2)
plt.plot([0, Av1[0]], [0, Av1[1]], 'r:')
plt.plot([0, Av2[0]], [0, Av2[1]], 'b:')

plt.legend()
plt.grid()
plt.axis('square')
plt.axis([-2,4,-3,4])
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Mon Aug  3 23:15:37 2020

@author: elewah
"""
# =============================================================================
# #Reference of algorthim that is used distribute the point over circumferece
# #https://stackoverflow.com/questions/48081690/how-to-find-points-on-a-circumference
# #get_2Dpoints function over a circumference,
# #input variable radius of the circle and number of points, the output is (nX2) cordinates-points matrix in dataframe format
# #where n is number of points and 2 is two columns (x,y)
# =============================================================================
import numpy as np
def get_2Dpoints(radius, number_of_points):
    radians_between_each_point = 2*np.pi/number_of_points
    list_of_points = []
    for p in range(0, number_of_points):
        list_of_points.append( (radius*np.cos(p*radians_between_each_point),radius*np.sin(p*radians_between_each_point)) )
    return list_of_points


# encoding=utf8

"""

Module containing functions and methods related to sequences in number theory such as
the Fibonacci sequence, the 3n + 1 problem, and more.

"""

import numpy as np
from mathpy.numtheory.integers import iseven
from decimal import Decimal, localcontext

def fibonacci(n, output='last', prec=100):
    r"""
    Computes the Fibonacci sequence up to given value of n.

    Parameters
    ----------
    n : int
        Integer designating the stopping point of the Fibonacci sequence
    output : {'last', 'array'}, optional
        If 'last', the last integer in the Fibonacci sequence up to n is returned,
        if 'array', the entire sequence is returned. Defaults to 'last'.
    prec : int default 100, optional
        Defines level of precision for factorials over 100
        for use by the decimal package

    Returns
    -------
    fn : numpy ndarray or int
        Last integer in Fibonacci sequence up to :math:`n` or the an array of the
        Fibonacci sequence up to :math:`n`.

    Notes
    -----
    The Fibonacci sequence is defined by a recurrence relation where :math:`f_n` denotes
    the series up to :math:`n` points.

    .. math::

        f_n = f_{n-1} + f_{n-2}

    With initial conditions:

    .. math::

        f_1 = 1, \qquad f_2 = 2

    Examples
    --------
    >>> fibonacci(10)
    55.0
    >>> fibonacci(10, 'array')
    array([  0.,   1.,   1.,   2.,   3.,   5.,   8.,  13.,  21.,  34.,  55.])

    References
    ----------
    Moler, C. (2011). Numerical computing with MATLAB (1st ed.).
        Philadelphia, Pa: Society for Industrial & Applied Mathematics.

    """
    fn = np.empty(n + 1)

    fn[0] = 0
    fn[1] = 1
    if n >= 100:
        with localcontext() as ctx:
            ctx.prec = prec
            for i in np.arange(2, n + 1):
                fn[i] = Decimal(fn[i - 1]) + Decimal(fn[i - 2])
    else:
        for i in np.arange(2, n + 1):
            fn[i] = fn[i - 1] + fn[i - 2]

    if output is 'last' or output is not 'array':
        return fn[-1]
    else:
        return fn

def three_n_one(n):
    r"""
    Computes the famous :math:`3n + 1` sequence, also known as the Collatz conjecture.

    Parameters
    ----------
    n : int
        Starting integer to begin :math:`3n + 1` process.

    Returns
    -------
    sequence : list
        The sequence generated by the Collatz conjecture. If n is 1, 1 is returned.

    Notes
    -----
    The Collatz conjecture, also known as the :math:`3n + 1` problem, is a currently
    unsolved problem in number theory that is stated as:

    - Start with a positive integer :math:`n`
    - If :math:`n` is even, divide :math:`n` by 2
    - If :math:`n` is odd, multiply by 3 and add 1 to obtain :math:`3n + 1`
    - Repeat this process until the sequence reaches 1

    References
    ----------
    3x + 1 problem. (2017, May 13). In Wikipedia, The Free Encyclopedia.
        from https://en.wikipedia.org/w/index.php?title=3x_%2B_1_problem&oldid=780191927

    Moler, C. (2011). Numerical computing with MATLAB (1st ed.).
        Philadelphia, Pa: Society for Industrial & Applied Mathematics.

    """
    if n == 1:
        return 1.0

    sequence = []

    while n > 1:
        if iseven(n):
            n /= 2.0
            sequence.append(n)
        else:
            n = 3 * n + 1
            sequence.append(n)

    return sequence


# content of test_sample.py
from numpy import pi
from sys import path
path.append('..')
from rotation import *

acceptable_error = .001

def test_2D_angle():
    for i in range(14):
        theta = i * pi / 6
        assert rotate2DimFrame(1, 0, theta)[0] == cos(theta)

def test_2D_length():
    assert rotate2DimFrame(1, 0, pi / 3)[0] - .5 < acceptable_error

def test_wrap_angle_pi():
    assert wrapAnglePi(1.5 * pi) - (-pi / 2) < acceptable_error

def test_wrap_angle_2pi():
    assert wrapAngle2Pi(-pi / 6) - 5 * pi / 6 < acceptable_error
    assert wrapAngle2Pi(7 * pi / 6) - pi / 6 < acceptable_error

def test_degree_to_radian():
    assert degreeToRadian(30) - pi / 6 < acceptable_error
    assert degreeToRadian(150) - 5 * pi / 6 < acceptable_error


import numpy as np
import matplotlib.pyplot as plt
#Solution of tridiagonal amtrix from 2.6 Eperson "An Introduction to Numerical Methods and Analysis"
#Of the form Ax=f
#l is lower diagonal, d is diagonal and u is the upper diagonal

#n=4
#l=np.array([np.NaN,1.0,1.0,1.0])
#d=np.array([4.0,4.0,4.0,4.0])
#u=np.array([1.0,1.0,1.0,np.NaN])
#f=np.array([6.0,12.0,18.0,19.0])
#x=np.array([0.0,0.0,0.0,0.0])
#
##Elimination stage
#for i in range(1,(n)):
#    d[i] = d[i] - u[i-1] *l[i] / d[i-1]
#    f[i] = f[i] - f[i-1] *l[i] / d[i-1]
#
##backsolve stage
#x[(n-1)]= f[(n-1)]/d[(n-1)]
#for i in range((n-2),-1,-1):
#    x[i] = (f[i] - u[i] * x[i+1])/d[i]
#
#print(x)

#Setting up the problem
m=20
h=1/(m+1)
u=(1/(h**2))*np.ones(m-1)
d=(1/(h**2))*(-2.0)*np.ones(m)
l=(1/(h**2))*np.ones(m-1)
F=np.empty(m)
U=np.empty(m)
x=np.arange(0,(1+h),h)
alpha=0
beta=1
#Edit for given function
def f(x):
    return (np.pi**2.0)*np.sin(np.pi*x)

#Creating F
F[0]=f(x[1]) - ( alpha/(h **2) )
F[m-1]=f(x[m-1]) - ( beta/(h **2) )
for i in range(1,m-1):
    F[i]=f(x[i+1])

#Solving the tridiagonal problem
#Elimination stage
for i in range(1,(m)):
    d[i] = d[i] - u[i-1] *l[i-1] / d[i-1]
    F[i] = F[i] - F[i-1] *l[i-1] / d[i-1]

#backsolve stage
U[(m-1)]= F[(m-1)]/d[(m-1)]
for i in range((m-2),-1,-1):
    U[i] = (F[i] - u[i] * U[i+1])/d[i]

#Insert end values
U=np.insert(U,0,alpha)
U=np.insert(U,m+1,beta)
#Print answer
print(U)


import scipy.integrate as spi
import numpy as np
import pylab as pl

beta=1.4247
gamma=0.14286
TS=1.0
ND=70.0
S0=1-1e-6
I0=1e-6
INPUT = (S0, I0, 0.0)

def diff_eqs(INP,t):
    '''The main set of equations'''
    Y=np.zeros((3))
    V = INP
    Y[0] = - beta * V[0] * V[1]
    Y[1] = beta * V[0] * V[1] - gamma * V[1]
    Y[2] = gamma * V[1]
    return Y   # For odeint

t_start = 0.0; t_end = ND; t_inc = TS
t_range = np.arange(t_start, t_end+t_inc, t_inc)
RES = spi.odeint(diff_eqs,INPUT,t_range)

print(RES)

#Ploting
pl.plot(RES[:,0], '-bs', label='Susceptibles')  # I change -g to g--  # RES[:,0], '-g',
pl.plot(RES[:,2], '-g^', label='Recovereds')  # RES[:,2], '-k',
pl.plot(RES[:,1], '-ro', label='Infectious')
pl.legend(loc=0)
pl.title('SIR epidemic without births or deaths')
pl.xlabel('Time')
pl.ylabel('Susceptibles, Recovereds, and Infectious')
pl.savefig('2.1-SIR-high.png', dpi=900) # This does, too
pl.show()


import numpy as np
from matplotlib import pyplot as plt

T=60 # final simulation time
N=100 # Step count
h=T/N # Step size

beta = 0.5 # S->I growth
gamma = 0.25 # I->R growth
rho1 = 0.01 # Group 1->2 growth
rho2 = 0.01 # Group 2->1 growth

Phi = np.zeros((N+1,6)) # Array for storing all step results
t = np.linspace(0,T,N+1) # All t values

Phi[0]=np.array([.95,.05,0,1,0,0]) # Initial value
S1, I1, R1, S2, I2, R2 = Phi[0] # ...

def f(t,Phi): # f(t,y) = y'
    S1, I1, R1, S2, I2, R2 = Phi # Extract pre-step values
    f = np.zeros(6) # Output array
    f[0] = -beta*S1*I1-rho1*S1+rho2*S2          # New S1
    f[1] = beta*S1*I1-gamma*I1-rho1*I1+rho2*I2  # New I1
    f[2] = gamma*I1-rho1*R1+rho2*R2             # New R1
    f[3] = -beta*S2*I2-rho2*S2+rho1*S1          # New S2
    f[4] = beta*S2*I2-gamma*I2-rho2*I2+rho1*I1  # New I2
    f[5] = gamma*I2-rho2*R2+rho1*R1             # New R2
    return f   # Return new values

for j in np.arange(0,N):  # For each step
    k1 = f(t[j],Phi[j])   # Compute k1
    k2 = f(t[j]+h/2,Phi[j]+h/2*k1) # Compute k2
    k3 = f(t[j]+h/2,Phi[j]+h/2*k2) # Compute k3
    k4 = f(t[j]+h,Phi[j]+h*k3) # Compute k4
    k = (k1+2*k2+2*k3+k4)/6 # Weighted average of ks
    Phi[j+1] = Phi[j] + h*k # Perform the step

plt.figure()  # Start a plot
plt.plot(t,Phi[:,0],'-',linewidth=1)   # Plot S1
plt.plot(t,Phi[:,1],'-',linewidth=1)   # Plot I1
plt.plot(t,Phi[:,2],'-',linewidth=1)   # Plot R1
plt.plot(t,Phi[:,3],'--',linewidth=1)   # Plot S2
plt.plot(t,Phi[:,4],'--',linewidth=1)   # Plot I2
plt.plot(t,Phi[:,5],'--',linewidth=1)   # Plot R2
plt.legend(['S_1','I_1','R_1','S_2','I_2','R_2'])  # Label each plot accordingly
plt.xlabel('t') # Label x axis
plt.show() # Finish and show the plot


#!/usr/local/bin/python3

import numpy as np

def load_data(data_path):
    points = list()
    with open(data_path, 'r') as f:
        lines = f.readlines()
        for line in lines:
            point_str = line.strip("\n").split("\t")
            points.append([float(point_str[0]), float(point_str[1])])
        points = np.array(points)
    return points

def ssei(cluster):
    # print(cluster)
    c = np.average(cluster, axis=0)
    # print(c)
    sse = 0
    for x in cluster:
        sse += np.square(np.linalg.norm(x-c))
    return sse

def sse(clusters):
    sse = 0
    for cluster in clusters:
        sse += ssei(cluster)
    return sse

def ssb(clusters, points):
    c = np.average(points, axis=0)
    ssb = 0
    for cluster in clusters:
        ci = np.average(cluster, axis=0)
        ssb += len(cluster) * np.square(np.linalg.norm(c-ci))
    return ssb

def tss(clusters, points):
    c = np.average(points, axis=0)
    tss = 0
    for point in points:
        tss += np.square(np.linalg.norm(point-c))
    return tss

def main():
    points = load_data("./6_points")
    print("================ a. ================")
    print("Cluster 1:")
    print(points[:3])
    print("Cluster 2:")
    print(points[3:])
    se1 = ssei(points[:3])
    print("SSE1 =",se1)
    se2 = ssei(points[3:])
    print("SSE2 =",se2)
    se = sse([points[:3], points[3:]])
    print("SSE =",se)
    sb = ssb([points[:3], points[3:]], points)
    print("SSB =",sb)
    ts = tss([points[:3], points[3:]], points)
    print("TSS =",ts)
    # print(se+sb)
    print("================ b. ================")
    cluster1 = np.array([points[0],points[5]])
    cluster2 = np.array([points[1],points[3]])
    cluster3 = np.array([points[2],points[4]])
    print("Cluster 1:")
    print(cluster1)
    print("Cluster 2:")
    print(cluster2)
    print("Cluster 3:")
    print(cluster3)
    se1 = ssei(cluster1)
    print("SSE1 =",se1)
    se2 = ssei(cluster2)
    print("SSE2 =",se2)
    se3 = ssei(cluster3)
    print("SSE3 =",se3)
    se = sse([cluster1,cluster2,cluster3])
    print("SSE =",se)
    sb = ssb([cluster1,cluster2,cluster3], points)
    print("SSB =",sb)
    ts = tss([cluster1,cluster2,cluster3], points)
    print("TSS =",ts)
    # print(se+sb)

if __name__ == "__main__":
    main()


#!/usr/bin/env python
# coding: utf-8

from sklearn import datasets
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error

# load dataset
X, y = datasets.load_boston(return_X_y=True)
print(X.shape)
print(y.shape)

# create virtual features, including
#   second degree of the first variable
#   second degrees of the eighth variable
#   third and second degrees of the eleventh variable
### Your code here ###
x1SecondDegree = X[:, 0] ** 2
x8SecondDegree = X[:, 7] ** 2
x11SecondDegree = X[:, 10] ** 2
x11ThirdDegree = X[:, 10] ** 3
virtualFeatures = np.stack((x1SecondDegree, x8SecondDegree, x11SecondDegree, x11ThirdDegree), axis=-1)

# concatenate the virtual feature to the original features
### Your code here ###
X = np.concatenate((X, virtualFeatures), axis=1)

# add a dimension with all 1 to account for the intercept term
interc = np.ones((X.shape[0], 1))
X = np.hstack((interc, X))
print(X.shape)

# split training and testing dataset
train_ratio = 0.8
cutoff = int(X.shape[0] * train_ratio)
X_tr = X[:cutoff, :]
y_tr = y[:cutoff]
X_te = X[cutoff:, :]
y_te = y[cutoff:]
print('Train/Test: %d/%d' % (X_tr.shape[0], X_te.shape[0]))

#
#
# linear regression using the normal equation
def pseudo_inverse(A):
    # Calculate the pseudo_inverse of A
    pinv = np.matmul(np.linalg.inv(np.matmul(A.T, A)), A.T)
    return pinv

# # fit the polynomial on the training set
inverse = pseudo_inverse(X_tr)
beta = np.matmul(inverse, y_tr)

#
#
# evaluation functions
def MSE(prediction, reference):
    # Calculate the mean square error between the prediction and reference vectors
    mse = mean_squared_error(reference, prediction)
    return mse

def MAE(prediction, reference):
    # Calculate the mean absolute error between the prediction and reference vectors
    mae = mean_absolute_error(reference, prediction)
    return mae

#
# make prediction on the testing set
pred = np.matmul(X_te, beta)  ### Your code here ###
mse = MSE(pred, y_te)
mae = MAE(pred, y_te)
print(mse)
print(mae)
print("------------------")
print("Regularized Regression!!")
print("------------------")
#
# regularized linear regression
def regularized_pseudo_inverse(A, theta):
    # Calculate the regularized pseudo_inverse of A
    I = np.identity(A.shape[1])
    pinv = np.matmul(np.linalg.inv(np.matmul(A.T, A) + theta*I), A.T)
    return pinv

# fit the polynomial, regularized by theta
theta = 0.5
beta_regularized = np.matmul(regularized_pseudo_inverse(X_tr, theta),y_tr)

#
# make prediction on the testing set
pred_2 = np.matmul(X_te, beta_regularized)
mse = MSE(pred_2, y_te)
mae = MAE(pred_2, y_te)
print(mse)
print(mae)


import numpy as np
import math

def combination(n, r):
    return math.factorial(n) / math.factorial(r) / math.factorial(n - r)

def pascal_matrix(n):
    """
    computes a pascal matrix such as
    [[1,1,1,1],
     [1,2,3,4],
     [1,3,6,10],
     [1,4,10,20]]
    """
    return np.matrix([[combination(x+y-2, y-1) for y in range(1,n+1)]
                      for x in range(1,n+1)], dtype='double')

def harmonic_vector(n):
    """
    create a vector in the form [1,1/2,1/3,...1/n]
    """
    return np.array([[1.0 / i] for i in range(1, n + 1)], dtype='double')

def mult(a, b):
    """
    assumes the given matrices are the correct shape
    """
    result = np.zeros((a.shape[0], b.shape[1]))
    for i in range(result.shape[0]):
        for j in range(result.shape[1]):
            # result[i,j] = a col i * b row j
            result[i,j] = np.dot(a[i], b[:,j])
    return result

def norm_inf(matrix):
    return max(abs(sum(np.nditer(row))) for row in matrix)

def norm(vector):
    return math.sqrt(sum(float(x)**2 for x in np.nditer(vector)))

def lu_fact(matrix):  # 1a
    """
    computes the LU factorization of a matrix
    """
    size = len(matrix)
    l_mat = np.zeros((size,size))
    u_mat = np.zeros((size,size)) + matrix
    for row in range(size):
        for col in range(size):
            if row == col:
                l_mat[row,col] = 1 / u_mat[row,col]
                u_mat[row] /= u_mat[row,col]
            elif row > col:
                l_mat[row, col] += u_mat[row,col] / u_mat[col,col]
                u_mat[row] -= (u_mat[row,col] / u_mat[col,col]) * u_mat[col]

    err = norm_inf(mult(l_mat, u_mat) - matrix)

    return l_mat, u_mat, err

def qr_fact_househ(matrix):  # 1b
    size = matrix.shape[1]
    big = max(matrix.shape)
    working = np.zeros(matrix.shape) + matrix  # copy
    hs = []
    for col in range(size-1):
        submatrix = working[col:,col:]  # work only on the submatrix
        v = submatrix[:,0]  # gets a column
        e = np.zeros(submatrix.shape[1])  # calculates e for the submatrix
        e[0] = 1
        u = v + norm(v) * e
        u /= norm(u)  # calculate u = (v + ||v|| * e) / ||(v + ||v|| * e)||
        H_part = np.eye(submatrix.shape[0]) - 2 * mult(np.matrix(u).T, np.matrix(u))
        H = np.zeros((big,big))
        for i in range(col):
            H[i,i] = 1
        H[col:,col:] = H_part
        working = mult(H, working)
        hs.append(H)

    q_mat = reduce(mult, hs, np.eye(size))
    r_mat = mult(reduce(mult, hs[::-1], np.eye(size)), matrix)
    err = norm_inf(mult(q_mat, r_mat) - matrix)

    return q_mat, r_mat, err

def qr_fact_givens(matrix):  # 1b
    size = matrix.shape[1]
    working = np.zeros(matrix.shape) + matrix
    gs = []
    for col in range(size):
        for row in range(col+1, size):
            x, y = working[col, col], working[row, col]
            sqrtx2y2 = math.sqrt(x**2 + y**2)
            c = x / sqrtx2y2
            s = -y / sqrtx2y2
            givensmat = np.eye(size)
            givensmat[col, col] = c
            givensmat[row, row] = c
            givensmat[col, row] = -s
            givensmat[row, col] = s
            working = mult(givensmat, working)
            gs.append(givensmat)

    q = reduce(mult, (g.T for g in gs), np.eye(size))
    r = mult(reduce(mult, gs[::-1], np.eye(size)), matrix)
    err = norm_inf(mult(q, r) - matrix)
    return q, r, err

def solve_lu_b(A, b):  # 1c
    l, u, err = lu_fact(A)
    b_copy = b.copy()
    c_vec = [0 for n in b]
    for row in range(len(b)):
        curr = b_copy[row]
        for col in range(row):
            curr -= l[row, col] * c_vec[col]
        c_vec[row] = curr / l[row, row]

    x_vec = [0 for n in b]
    for row in range(len(b)-1, -1, -1):
        curr = c_vec[row]
        col = len(b) - 1
        while col > row:
            curr -= u[row, col] * x_vec[col]
            col -= 1
        x_vec[row] = curr / u[row, row]

    x_vec = np.array(x_vec)
    sol_err = norm_inf(mult(A, x_vec) - b)
    return x_vec, err, sol_err

def solve_qr_b(A, b):  # 1c
    q, r, err = qr_fact_househ(A)
    c_vec = mult(q.T, b)

    x_vec = [0 for n in b]
    for row in range(len(b)-1, -1, -1):
        curr = c_vec[row]
        col = len(b) - 1
        while col > row:
            curr -= r[row, col] * x_vec[col]
            col -= 1
        x_vec[row] = curr / r[row, row]

    x_vec = np.array(x_vec)
    sol_err = norm_inf(mult(A, x_vec) - b)
    return x_vec, err, sol_err

def load_matrix(file):
    with open(file) as f:
        return np.matrix([line.split(",") for line in f.readlines()], dtype='double')

def convert_matrix(m):
    A = m[:, :-1]
    b = m[:, -1]
    return A, b

def main():  # 1d
    print "solving with lu"
    for i in range(2, 13):
        x, err, sol_err = solve_lu_b(pascal_matrix(i), harmonic_vector(i))
        print i
        print x
        print "norm(LU-P), norm(Px-b)"
        print err, sol_err
        print ""

    print "solving with qr"
    for i in range(2, 13):
        x, err, sol_err = solve_qr_b(pascal_matrix(i), harmonic_vector(i))
        print i
        print x
        print "norm(QR-P), norm(Px-b)"
        print err, sol_err
        print ""


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test-scipy-butterworth_lowpass_filter.py

Creating lowpass filter in SciPy - understanding methods and units
https://stackoverflow.com/questions/25191620/creating-lowpass-filter-in-scipy-understanding-methods-and-units

Butterworth Bandpass
https://scipy-cookbook.readthedocs.io/items/ButterworthBandpass.html

"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import butter, lfilter, freqz

def butter_lowpass(cutoff, fs, order=5):
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = butter(order, normal_cutoff, btype='low', analog=False)
    return b, a

def butter_lowpass_filter(data, cutoff, fs, order=5):
    b, a = butter_lowpass(cutoff, fs, order=order)
    y = lfilter(b, a, data)
    return y

def run():
    # Filter requirements.
    order = 6
    fs = 30.0       # sample rate, Hz
    cutoff = 3.667  # desired cutoff frequency of the filter, Hz

    # Plot the frequency response for a few different orders.
    plt.figure(1)
    plt.clf()
    for order in [3, 6, 9]:
        b, a = butter_lowpass(cutoff, fs, order)
        w, h = freqz(b, a, worN=8000)
        plt.plot((fs * 0.5 / np.pi) * w, abs(h), label="order = %d" % order)

    plt.plot(0.5*fs*w/np.pi, np.abs(h), 'b')
    plt.plot(cutoff, 0.5*np.sqrt(2), 'ko')
    plt.axvline(cutoff, color='k')
    plt.xlim(0, 0.5*fs)
    plt.title("Lowpass Filter Frequency Response")
    plt.xlabel('Frequency [Hz]')
    plt.ylabel('Gain')
    plt.grid()
    plt.legend(loc='best')
    plt.plot([0, 0.5 * fs], [np.sqrt(0.5), np.sqrt(0.5)], '--', label='sqrt(0.5)')

    # Filter a noisy signal.
    T = 10.00
    nsamples = T * fs
    t = np.linspace(0, T, nsamples, endpoint=False)
    a = 0.02
    f0 = 600.0
    x = 0.1 * np.sin(2 * np.pi * 1.2 * np.sqrt(t))
    x += 0.01 * np.cos(2 * np.pi * 312 * t + 0.1)
    x += a * np.cos(2 * np.pi * f0 * t + .11)
    x += 0.03 * np.cos(2 * np.pi * 2000 * t)
    plt.figure(2)
    plt.clf()
    plt.plot(t, x, label='Noisy signal')

    y = butter_lowpass_filter(x, cutoff, fs, order=6)
    plt.plot(t, y, label='Filtered signal (%g Hz)' % f0)
    plt.xlabel('time (seconds)')
    plt.hlines([-a, a], 0, T, linestyles='--')
    plt.grid()
    plt.axis('tight')
    plt.legend(loc='upper left')

    plt.show()

run()


import numpy as np
import random, math

def algoAllPairs(cost, A, n):
	for i in range(n):
		for j in range(n):
			A[i][j] = cost[i][j]

	for k in range(n):
		for i in range(n):
			for j in range(n):
				A[i][j] = min(A[i][k] + A[k][j], A[i][j])

	return np.matrix(A)

n = random.randint(5, 7)
e = [[0 for x in range(n)] for y in range(n)]
c = [[0 for x in range(n)] for y in range(n)]

for i in range(n):
	for j in range(n):
		if (i!= j):
			e[i][j] = 1
			c[i][j] = random.randint(10,40)

print ('Number of vertices: ', n)
print ('Edge Adjacency Matrix: \n',np.matrix(e))
print ('Cost Adjacency Matrix: \n',np.matrix(c))
A = [[0 for x in range(n)] for y in range(n)]

print ('Minimum Cost Adjacency Matrix: \n', algoAllPairs(c, A, n))


'''
Are We Confident This Stock is Mean Reverting?

In the last chapter, you saw that the autocorelation of MSFT's weekly stock returns was -0.16. That autocorrelation seems large, but is it statistically significant? In other words, can you say that there is less than a 5% chance that we would observe such a large negative autocorrelation if the true autocorrelation were really zero? And are there any autocorrelations at other lags that are significantly different from zero?

Even if the true autocorrelations were zero at all lags, in a finite sample of returns you won't see the estimate of the autocorrelations exactly zero. In fact, the standard deviation of the sample autocorrelation is 1/N‾‾√
1
/
N
 where N
N
 is the number of observations, so if N=100
N
=
100
, for example, the standard deviation of the ACF is 0.1, and since 95% of a normal curve is between +1.96 and -1.96 standard deviations from the mean, the 95% confidence interval is ±1.96/N‾‾√
±
1.96
/
N
. This approximation only holds when the true autocorrelations are all zero.

You will compute the actual and approximate confidence interval for the ACF, and compare it to the lag-one autocorrelation of -0.16 from the last chapter. The weekly returns of Microsodt is pre-loaded in a DataFrame called returns.

INSTRUCTIONS
100XP
INSTRUCTIONS
100XP
Recompute the autocorrelation of weekly returns in the Series 'Adj Close' in the returns DataFrame.
Find the number of observations in the returns DataFrame using the len() function.
Approximate the 95% confidence interval of the estimated autocorrelation. The math function sqrt() has been imported and can be used.
Plot the autocorrelation function of returns using plot_acf that was imported from statsmodels. Set alpha=0.05 for the confidence intervals (that's the default) and lags=20.
'''
# Import the plot_acf module from statsmodels and sqrt from math
from statsmodels.graphics.tsaplots import plot_acf
from math import sqrt

# Compute and print the autocorrelation of MSFT weekly returns
autocorrelation = returns['Adj Close'].autocorr()
print("The autocorrelation of weekly MSFT returns is %4.2f" %(autocorrelation))

# Find the number of observations by taking the length of the returns DataFrame
nobs = len(returns)

# Compute the approximate confidence interval
conf = 1.96/sqrt(nobs)
print("The approximate confidence interval is +/- %4.2f" %(conf))

# Plot the autocorrelation function with 95% confidence intervals and 20 lags using plot_acf
plot_acf(returns, alpha=0.05, lags=20)
plt.show()


#Code to produce values for cumulated curvature of the cosine function

import numpy as np

import scipy.integrate as integrate

import matplotlib.pyplot as plt

#############
#Functions for Curvature for Cosine
#############

def cos_curvature(delta):
	return np.absolute(-np.cos(delta))/(1+(np.sin(delta)**2))

#########################
#Functions for Partitioning the domain into Q pieces of equal curvature
#########################

def curvature_target_x_value(integration_lb, interval_lb, interval_ub, target_value, eps = 0.000001):
	#Finds an approximation for a value x in the domain where accumulated curvature is equal to a target value
	#The strategy is to recursively narrow down the given interval into smaller and smaller intervals until an acceptable point is found.
	x = np.linspace(interval_lb, interval_ub)
	i = 0
	for value in x:
		cumulated_curvature = integrate.quad(cos_curvature, integration_lb, value)[0]
		if np.abs(cumulated_curvature - target_value) <= eps:
			return value
		if target_value - cumulated_curvature > eps:
			i = i + 1
			continue
		if cumulated_curvature - target_value > eps:
			return curvature_target_x_value(integration_lb, x[i-1], x[i], target_value, eps)

def eq_curvature_partition(lb, ub, Q, eps = 0.000001):
	#Divides the domain [lb, ub] into Q pieces of (approximately) equal curvature
	if Q == 1:
		return [lb, ub]
	breakpoints = [lb]
	target_value = (integrate.quad(cos_curvature, lb, ub)[0])/Q
	for i in range(Q-1):
		breakpoints.append(curvature_target_x_value(breakpoints[i], breakpoints[i], ub, target_value, eps))
	breakpoints.append(ub)
	return breakpoints

def eq_cosine_partition(lb, ub, Q):
	#Divides the domain [lb, ub] into Q pieces of equal curvature for the cosine function. Domain must be contained in (-pi/2, pi/2)
	if Q==1:
		return [lb, ub]
	total_curvature = np.arctan(np.sin(ub)) - np.arctan(np.sin(lb))
	breakpoints = [lb]
	for i in range(Q-1):
		breakpoints.append(np.arcsin(np.tan(total_curvature/Q + np.arctan(np.sin(breakpoints[i])))))
	breakpoints.append(ub)
	return breakpoints

if __name__ == '__main__':

    partition = eq_curvature_partition(-np.pi/6, np.pi/6, 16)

    cos_partition = eq_cosine_partition(-np.pi/6, np.pi/6, 16)

    print("Unrefined partition:", partition)

    print("Cosine partition:", cos_partition)

    x = np.linspace(-np.pi/6, np.pi/6)

    y1 = np.cos(x)

    plt.plot(x, y1)

    plt.show()

    for i in range(len(partition) - 1):
        x = np.linspace(partition[i], partition[i+1])
        y = np.cos(x)
        plt.plot(x, y)

    plt.show()

    for i in range(len(cos_partition) - 1):
    	x = np.linspace(cos_partition[i], cos_partition[i+1])
    	y = np.cos(x)
    	plt.plot(x, y)

    plt.show()


from random import sample
from typing import List

import numpy as np
from scipy.spatial.distance import euclidean

Point = List[int]

def assign_cluster(point: Point, centroids: List[Point]) -> int:
    """
    return index of closest centroid for a given point
    """
    min_distance = float("inf")
    closest_centroid_idx = None
    for idx, centroid in enumerate(centroids):
        distance = euclidean(point, centroid)
        if distance < min_distance:
            min_distance = distance
            closest_centroid_idx = idx

    return closest_centroid_idx

def calculate_variation(points: List[Point]) -> float:
    """
    return the maximum distance between any two points in a list
    """
    max_distance = 0
    for i in range(len(points)):
        for j in range(len(points)):
            distance = euclidean(points[i], points[j])
            max_distance = distance if distance > max_distance else max_distance

    return max_distance

def is_within_threshold(
    centroids: List[Point], means: List[Point], threshold: float
) -> bool:
    for centroid, mean in zip(centroids, means):
        distance = euclidean(centroid, mean)
        if distance > threshold:
            return False

    return True

def kmeans(
    points: List[Point],
    k: int = 3,
    runs: int = 10,
    max_iter: int = 300,
    threshold: float = 0.0001,
) -> List[Point]:
    """
    return a list of cluster centroids, given a list of points and the
    number of clusters to identify
    """
    if k > len(points):
        raise ValueError("number of clusters (k) must be less than number of points")

    dimensions = {len(p) for p in points}
    if len(dimensions) > 1:
        raise ValueError("points must all have the same dimensions")

    min_variation: float = float("inf")
    best_cluster: List[Point] = None

    for i in range(runs):
        centroids = sample(points, k)
        for i in range(max_iter):
            clusters = [[] for c in centroids]
            for p in points:
                closest_centroid_idx = assign_cluster(p, centroids)
                clusters[closest_centroid_idx].append(p)

            means = [np.mean(c, axis=0).tolist() for c in clusters]
            if is_within_threshold(centroids, means, threshold):
                break

            centroids = means

        variation = sum([calculate_variation(c) for c in clusters])
        if variation < min_variation:
            min_variation = variation
            best_cluster = centroids

    return best_cluster

if __name__ == "__main__":
    # test data are one-dimensional points with three clear centroids at 2, 12, and 22
    points = [[1], [2], [3], [11], [12], [13], [21], [22], [23]]
    centroids = kmeans(points, k=3)
    assert len(centroids) == 3
    assert [2] in centroids
    assert [12] in centroids
    assert [22] in centroids

    # centroids and means with the same values should always be within threshold
    centroids = [[1], [2], [3]]
    means = [[1], [2], [3]]
    threshold = 0.0001
    assert is_within_threshold(centroids, means, threshold) == True

    # centroids and means not within threshold
    centroids = [[1], [2], [3]]
    means = [[100], [200], [300]]
    threshold = 0.0001
    assert is_within_threshold(centroids, means, threshold) == False


# -*- coding: utf-8 -*-
"""
Notes and tries on Chaper 02 (Think Stats 2, Allen B. Downey)

Self-study on statistics using pyhton
@author: Github: @rafaelmm82
"""

import thinkstats2
import thinkplot
import nsfg
import math
import matplotlib.pyplot as plt
import numpy as np

hist = thinkstats2.Hist([1, 2, 2, 3, 5])
hist

hist.Freq(2)

hist[2]

hist.Freq(4)

hist.Values()

for val in sorted(hist.Values()):
    print(val, hist[val])

for val, freq in hist.Items():
    print(val, freq)

thinkplot.Hist(hist)
thinkplot.Config(xlabel='value', ylabel='frequency', legend='false')

preg = nsfg.ReadFemPreg()
live = preg[preg.outcome == 1]

# 2.1
hist = thinkstats2.Hist(live.birthwgt_lb, label='birthwgt_lb')
thinkplot.Hist(hist)
thinkplot.Config(xlabel='pounds', ylabel='frequency')

# 2.2
hist = thinkstats2.Hist(live.birthwgt_oz, label='birthwgt_oz')
thinkplot.Hist(hist)
thinkplot.Config(xlabel='ounces', ylabel='frequency')

# 2.3
hist = thinkstats2.Hist(np.floor(live.agepreg), label='agepreg')
thinkplot.Hist(hist)
thinkplot.Config(xlabel='agepreg', ylabel='frequency')

# 2.4
hist = thinkstats2.Hist(live.prglngth, label='prglength')
thinkplot.Hist(hist)
thinkplot.Config(xlabel='pregnancy weeks', ylabel='frequency')

for weeks, freq in hist.Smallest(10):
    print(weeks, freq)

for weeks, freq in hist.Largest(10):
    print(weeks, freq)

# First Babies

firsts = live[live.birthord == 1]
others = live[live.birthord != 1]

firsts_hist = thinkstats2.Hist(firsts.prglngth)
others_hist = thinkstats2.Hist(others.prglngth)

width = 0.45
thinkplot.PrePlot(2)
thinkplot.Hist(firsts_hist, align='right', width=width)
thinkplot.Hist(others_hist, align='left', width=width)
thinkplot.Config(xlabel='weeks', ylabel='Count', xlim=[27, 46])

plt.hist(firsts_hist, 20, color='r')
plt.hist(others_hist, 20, color='b')
plt.show()

def CohenEffectSize(group1, group2):
    diff = group1.mean() - group2.mean()

    var1 = group1.var()
    var2 = group2.var()
    n1, n2 = len(group1), len(group2)

    pooled_var = (n1 * var1 + n2 * var2) / (n1 + n2)
    d = diff / math.sqrt(pooled_var)
    return d

cohendiff = CohenEffectSize(firsts.prglngth, others.prglngth)

"""
 Exerc 2.1
 Based on about nine thousands pregnancies records from a great american census
 , a group of data scientits conducteded some analysis to understanding if
 there are any traces or elements to affirm or not that firsts babies come
 or not early them others. The conclusions, based on statistical derivative
 measurements, the fact graph analysis and standard and numerical accepted
 procedures, they concluded that there are no effective evidence that first
 babies tend to born too early ro too later them others.

"""


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time     : 2018/09/13 14:25
# @Author   : Iydon
# @File     : course2.2.py
import numpy as np
import time, random, threading

def tic(): globals()['TIC_TIME'] = time.time()
toc = lambda :time.time()-globals()['TIC_TIME']

def Newton_method(f, df, start:float=0.0, max_step:int=32, sign_dig:int=6)->float:
    """
    Newton method.
    ---------------------------
    Args:
        None.

    Returns:
        None.

    Raises:
        None.
    """
    fun = lambda x: x - f(x)/df(x)
    return fixed_point(fun, start, max_step, sign_dig)

def fixed_point(fun, start:float, max_step:int, sign_dig:int)->float:
    """
    Fixed-point iteration to find the zero.
    ----------------------------
    Args:
        fun: Function.
        start: Float, the first iteration point.
        max_step: Integer, max number of iteration.
        sign_dig: Integer, significant digits.

    Returns:
        Float zero.
        zero | fun(zero)\\sim 0.

    Raises:
        None.
    """
    fl = lambda x: round(x, 100)
    eps = 10**(-sign_dig)
    new_val = fun(start)
    for i in range(0, max_step):
        old_val = fl(new_val)
        new_val = fl(fun(old_val))
        if abs(old_val-new_val)<=2*eps:
            return (i, new_val)
    return "Max_step..."

def secant_method(f, start:list, max_step:int=32, eps:float=1e-6) -> float:
    """
    Secant method.
    ----------------------------
    Args:
        f: Function.
        start: List of float, the first iteration point.
        max_step: Integer, max number of iteration.

    Returns:
        Float zero.
        zero | fun(zero)\\sim 0.

    Raises:
        None.
    """
    p = [i    for i in start]
    q = [f(i) for i in start]
    for i in range(max_step):
        _p = p[-1] - q[-1]*(p[-1]-p[0])/(q[-1]-q[0])
        if abs(_p-p[-1])<eps:
            return (i, _p)
        p = [p[-1], _p]
        q = [q[-1], f(_p)]
    return False

def false_position(f, start:list, max_step:int=32, eps:float=1e-6) -> float:
    """
    False position.
    ----------------------------
    Args:
        f: Function.
        start: List of float, the first iteration point.
        max_step: Integer, max number of iteration.

    Returns:
        Float zero.
        zero | fun(zero)\\sim 0.

    Raises:
        None.
    """
    p = [i    for i in start]
    q = [f(i) for i in start]
    for i in range(max_step):
        _p = p[-1] - q[-1]*(p[-1]-p[0])/(q[-1]-q[0])
        if abs(_p-p[-1]) < eps:
            return (i, _p)
        _q = f(_p)
        if _q*q[-1] < 0:
            p[0] = p[-1]
            q[0] = q[-1]
        p[-1] = _p
        q[-1] = _q
    return False

def diff(f, eps:float=1e-6, left_flag=True):
    """
    \\frac{f(x+eps)-f(x)}{eps}.
    """
    if isinstance(f, type(lambda:0)):
        if left_flag:
            return lambda x: (f(x+eps)-f(x))/eps
        else:
            return lambda x: (f(x)-f(x-eps))/eps

f = lambda x: np.cos(x)
df = lambda x: -np.sin(x)
result = Newton_method(f, df, 9)
print(result)

df = diff(f, eps=1e-3)
result = Newton_method(f, df, 9)
print(result)

result = secant_method(f, [0,1])
print(result)

result = false_position(f, [0,1])
print(result)


# -*- coding: utf-8 -*-
"""
Created on Tue Mar 14 09:34:17 2017
@author: lamahamadeh
"""

'''
Case Study about an introduction to kNN classifiction
'''

#Finding the Euclidean Distance Between Two Points
#---------------------------------------------------

import numpy as np

def distance(p1,p2):
    "find the distance betweeen points p1 and p2"
    return np.sqrt(np.sum(np.power(p2 - p1, 2))) #Euclidean distance

p1 = np.array([1,1]) #p1=(x1,y1)
p2 = np.array([4,4]) #p2=(x2,y2)

print (distance(p1,p2))

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

#Majority Vote
#-------------

import random

def majority_vote(votes):
    "Return the most common element in votes"
    vote_counts = {}
    for vote in votes:
        if vote in vote_counts:
            vote_counts[vote] += 1
        else:
            vote_counts[vote] = 1
    #items method
    winners = []
    max_counts = max(vote_counts.values()) #ask Python about the frequency the most appearing value has
    for vote, count in vote_counts.items(): #this returns a tuple of (vote,count)
        if count == max_counts:
            winners.append(vote)

    return random.choice(winners) #in case of a tie situation, Python picks a random candidate (element) and returns it.

votes = [1, 2, 3, 1, 2 ,3 ,3 ,3 ,3, 2, 2, 2] #here number 2 and 3 has the same appearing frequency: 5 (tie situation)
winner = majority_vote(votes)
print (winner) #it can be seen after running the code several times that this method picks one of the candidates
#and returns it randomly.

'''
#another method
#--------------
import scipy.stats as ss

def majority_vote_short(votes):
    "Return the most common element in votes "
    mode , count = ss.mstats.mode(votes)
    return mode

votes = [1, 2, 3, 1, 2 ,3 ,3 ,3 ,3]
winner_short = majority_vote_short(votes)
print (winner_short) # it can be seen after running the code several times that this method always
#returns number 3 as the most common candidate and forgets about the other one: 2
'''

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

#Finding Nearest Neighbors
#--------------------------

points = np.array([[1,1], [1,2], [1,3], [2,1], [2,2], [2,3], [3,1], [3,2], [3,3]]) #create a numebr of points as a vector(training data)/predictors
p = np.array([2.5,2])#a single point with coordinates: x=2.5 and y=2 (testing data)

#plotting the points and point p
import matplotlib.pyplot as plt

plt.plot(points[:,0], points[:,1],"ro") #training points/data
plt.plot(p[0],p[1],"bo") #testing point/data
plt.axis([0.5, 3.5, 0.5, 3.5])

distances = np.zeros(points.shape[0])#'.shape[0]' here reutrn a tuple of array dimensions. this creates a ndarray where
#its elements are all zeros and has the size of 'points' array, i.e., 9.
print (distances); print(len(distances))

for i in range(len(distances)):# loop over all points
    distances[i] = distance(p,points[i]) # compute the distance between the testing point p and every other training points using the function 'distnace'

print(distances)#reutrn the distances values between all the points and the point p
#it can be seen from the plot that point[2,2] and point[3,2] are the nearest to the testing point p[2.5,2]
#this can be proved by calculating the distance between the testing point p and these two points,, i.e., distance=0.5.
print(points[4])#return the training point number 4: p4=[2,2]
print(distances[4])#calculate the distnace between the testing point p and the training point p4
print(points[7])#return the training point number 7: p7=[3,2]
print(distances[7])#calculate the distnace between the testing point p and the training point p7

#construct a 'find_nearest_neighbor' function
def find_nearest_neighbor(p, points, k=5): #k=5 is a default value, you can choose any value for k afterwards up to 5.
    """find the k nearest neighbors of point p nd return their indices """
    distances = np.zeros(points.shape[0])#'.shape[0]' here reutrn a tuple of array dimensions.
    for i in range(len(distances)):# loop over all points
        distances[i] = distance(p,points[i]) # compute the distance between point p and every other point using the function 'distnace'
    ind = np.argsort(distances)#'argsort' is a function that sorts the distnaces indinces from short to long.
    #np.argsort(distances) it sorts an array according to a single argument and returns the 'sorted indices'
    return ind[0:k] # or for short ind[:k]. This step is to return those k points that are nearest to point p

#k=2
ind = find_nearest_neighbor(p, points, 2)
print(ind) #return the indices of the nearest points ind[0:2]
print(points[ind])# return the nearest points

#k=3
ind = find_nearest_neighbor(p, points, 3)
print(ind) #return the indices of the nearest points ind[0:3]
print(points[ind])# return the nearest points

#k=4
ind = find_nearest_neighbor(p, points, 4)
print(ind) #return the indices of the nearest points ind[0:4]
print(points[ind])# return the nearest points

#construct a 'knn_predict' function
def knn_predict(p, points, outcomes, k=5):
    #find k nearest neighbors
    ind = find_nearest_neighbor(p, points, k)
    return majority_vote(outcomes[ind])
    #predict the class/categroy/target of the testing point p based on majority vote of the nearest training points

outcomes = np.array([0,0,0,0,1,1,1,1,1]) # an array for classes definition/targets: we have two numeric classes: 0 and 1

print(len(outcomes))#the same as the points array length

print(knn_predict(np.array([2.5, 2.7]), points, outcomes, k=2)) #class 1
print(knn_predict(np.array([1.0, 2.7]), points, outcomes, k=2)) #class 0

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

#Generating Synthetic Data
#-------------------------

from scipy import stats as ss

def generate_synthetic_data(n=50): #n is the number of generated random training points from normal distribution
    """Create two sets of points from bivariate normal distributions."""
    points = np.concatenate((ss.norm(0,1).rvs((n,2)),ss.norm(1,1).rvs((n,2))), axis=0) #norm(mean, standard deviation)
    #'.rvs' Random variates of given type. Here we have: .rvs((number of rows, number of columns))
    # 'axis = 0' means that we are concatenating along the rows of these arrays
    #the whole lemgth/size of points array is 2nx2: nx2 for the first generated points set and nx2 for the second one
    outcomes = np.concatenate((np.repeat(0,n), np.repeat(1,n)), axis=0) #generate two ndarrays/classes/outcomes/targets: the first one has 0 values and length(n)
    #and the second one with values 1 and length n.
    #0 and 1 here refer to the names of classes
    #axis =0 means that the concatenating happens along the rows
    return (points, outcomes)

n=20

(points, outcomes) = generate_synthetic_data(n) #this gives 20 points from class 0 and the other 20 from class 1

plt.figure()
plt.plot(points[:n,0], points[:n,1], "ro") #this refers to the first class 0 (n points)
plt.plot(points[n:,0], points[n:,1], "bo") #this refers to the second class 1 (n points)

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

#Making a Prediction Grid
#-------------------------

def make_prediction_grid(predictors, outcomes, limits, h, k):
    """classify each point on the prediction grid."""
    (x_min, x_max, y_min, y_max) = limits
    xs = np.arange(x_min, x_max, h) #h is step size
    ys = np.arange(y_min, y_max, h) #h is step size
    xx, yy = np.meshgrid(xs, ys)#this creates a two dimensional array

    prediction_grid = np.zeros(xx.shape, dtype = int) # the type here is 'int' becasue both classes are integers: either 0 or 1
    for i,x in enumerate(xs):
        for j,y in enumerate(ys):
            p = np.array([x,y])
            prediction_grid[j,i] = knn_predict(p, predictors, outcomes, k)
            #It can be noticed here that we have put [j,i] not the other way around. The reason for that is
            # that in the prediction grid, y always refers to the rows of the grid
            #however, x refers to the columns of the grid.
            #It is important in the square brackets to begin with the the first eleemnt corresponds to the rows of the array
            #and the second element corrsponds to the coloumn of the array.
    return (xx, yy, prediction_grid)

'''
NOTE about enumerate:
---------------------
enumerate return a tuple where the first element is the index and the second element is the specific object itself.
for example: if we define a list of season as:
seasons = ['spring', 'winter', 'summer', 'autumn']
enumerate(seasons) #this does nothing
list(enumerate(seasons))#this returns tuples that contain the index of each season being the first element of the tuple
#and the name of the season being the second element of the tuple.
we can illustrate this using for loopas following:

for ind,season in enumerate(seasone):
    print(ind,season)
and as we have seen before this return tuples of all the element in seasons list.
'''
#------------------------------------------------------------------------------

#Plotting the Prediction Grid
#-----------------------------

def plot_prediction_grid (xx, yy, prediction_grid, filename):
    """ Plot KNN predictions for every point on the grid."""
    from matplotlib.colors import ListedColormap
    background_colormap = ListedColormap (["hotpink","lightskyblue", "yellowgreen"])
    observation_colormap = ListedColormap (["red","blue","green"])
    plt.figure(figsize =(10,10))
    plt.pcolormesh(xx, yy, prediction_grid, cmap = background_colormap, alpha = 0.5)
    plt.scatter(predictors[:,0], predictors [:,1], c = outcomes, cmap = observation_colormap, s = 50)
    plt.xlabel('Variable 1'); plt.ylabel('Variable 2')
    plt.xticks(()); plt.yticks(())
    plt.xlim (np.min(xx), np.max(xx))
    plt.ylim (np.min(yy), np.max(yy))
    plt.savefig(filename)

(predictors, outcomes) = generate_synthetic_data() #in case you don't define a value for n it will be the default value defined in the function, i.e, n=50.

k = 5; filename = "knn_synth_5.pdf"; limits = (-3, 4, -3 ,4); h = 0.1
(xx, yy, prediction_grid) = make_prediction_grid(predictors, outcomes, limits, h ,k)
plot_prediction_grid(xx, yy, prediction_grid, filename)

k = 50; filename = "knn_synth_50.pdf"; limits = (-3, 4, -3 ,4); h = 0.1
(xx, yy, prediction_grid) = make_prediction_grid(predictors, outcomes, limits, h ,k)
plot_prediction_grid(xx, yy, prediction_grid, filename)

#Looking at the plot for k equals 50, we can see that the decsion boundary is pretty smooth.
#In contrast, if you look at the plot where k equals to 5, you'll see that the shape of the descison boundary is more complicated
#which means that k contriols the smoothness of the fit.
#It seems that you might be able to find a value of k that maximises the accuracy of the predictions.
#But that's somewhat short sighted.
#this is because what you really care about is not how well your method performs on the training data set,
#the data set that we have seen so far.
#but rather how well it performs on a future dataset you have not yet seen.
#It turns out that using a value for k that is too large or too small is not optimal.
# a phenomenon that is known as the bias-variance tradeoff. This suggests that some intermediate values of k
#might be best.

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

#Applying the kNN Method
#------------------------

from sklearn import datasets
iris = datasets.load_iris() #iris is a built-in dataset in sklearn

print iris['data'] #the size of the dataset is 150x4: 150 rows (observations) and 4 columns (covariates:sepal length, sepal width, petal length and petal width )

predictors = iris.data[:, 0:2] #subset
outcomes = iris.target #classes

plt.plot(predictors[outcomes==0][:,0], predictors[outcomes==0][:,1], 'ro') #the first species/ feature that is contained in the iris dataset
plt.plot(predictors[outcomes==1][:,0], predictors[outcomes==1][:,1], 'go') #the second species/ feature that is contained in the iris dataset
plt.plot(predictors[outcomes==2][:,0], predictors[outcomes==2][:,1], 'bo') #the third species/ feature that is contained in the iris dataset
plt.savefig('iris.pdf')

#prediction plot
k = 5; filename = "iris_grid.pdf"; limits = (4,8,1.5,4.5); h = 0.1
(xx, yy, prediction_grid) = make_prediction_grid(predictors, outcomes, limits, h ,k)
plot_prediction_grid(xx, yy, prediction_grid, filename)#print the training data

my_predictions = np.array([knn_predict(p,predictors, outcomes,5) for p in predictors])
print my_predictions.shape # (150,)

#compare between our own-made knn classifier and the sklearn classifier
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors = 5)
knn.fit(predictors, outcomes)
sk_predictions = knn.predict(predictors)

print sk_predictions.shape #(150,)

sk_predictions == my_predictions

print np.mean(sk_predictions == my_predictions)
print 100*np.mean(sk_predictions == my_predictions) #precentage of how both methods are close

print 100*np.mean(sk_predictions == outcomes) #83.333
print 100*np.mean(my_predictions == outcomes) #84.666

# It can be seen clearly that our own-made predictor is slightly better than the sklearn predictior: it is accurate approximately 85% of the time.

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


# distance, dot- and crossproduct of vectors through class
import math
import numpy as np

class Vector():
    """numpydoc:
    A new class Vector with functions
    to calculate the distance, the dot
    and cross products of vectors.

    Attributes
    ----------
    x, y, z : 'float'
        The vectors coordinates.

    Returns
    -------
    ret1 : float
        Distance/Magnitude between two vectors
    ret2 : float
        Dot product between two vectors
    ret3 : floats
        cross product between two vectors
        three floats representing the resulting vector

    Note:
    ----
    .. _NumPy Documentation HOWTO:
        https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
    pro: Better for complex descriptions and documentation
    con: Uses more vertical space
    """

    def __init__(self, x, y, z):
        """Construction of the necessary attributes for the vector.

        Parameters
        ----------
        x, y, z : 'float'
            The vectors coordinates.
        """

        self.x = x
        self.y = y
        self.z = z

    # function for magnitude between two vectors (betrag)
    def distance(self, other):
        """Prints the distance between two vectors
        if the coordinates for the other vector is passed.

        Parameters
        ----------
        other : floats
            actually a list of floats representing vector.

        Returns
        -------
        ret1 : float
            Distance/Magnitude between two vectors
        """
        return math.sqrt((other.x - self.x)**2 + (other.y - self.y)**2 + (other.z - self.z)**2)

    def dot(self, other):
        """Prints the dot product between two vectors
        if the coordinates for the other vector is passed.

        Parameters
        ----------
        other : floats
            actually a list of floats representing vector.

        Returns
        -------
        ret1 : float
            Dot product between two vectors
        """
        return ((self.x * other.x) + (self.y * other.y) + (self.z * other.z))

    def cross(self, other):
        """Prints the cross product between two vectors
        if the coordinates for the other vector is passed.

        Parameters
        ----------
        other : floats
            actually a list of floats representing vector.

        Returns
        -------
        ret2 : floats
            cross product between two vectors
        """
        return Vector((self.y * other.z) - (self.z * other.y),
        (self.x * other.z) - (self.z * other.x),
        (self.x * other.y) - (self.y * other.x))

    # formatting output of vector
    def __repr__(self):
        """Returns the object representation. In this case string.

        Parameters
        ----------
        self : floats
            actually a list of floats representing vector.

        Returns
        -------
        ret3 : sring
            cross product between two vectors formatted.
        """
        out = "x = " + str(self.x)

        if self.y >= 0:
            out = out + "+ "
        out = out + "\n y = " + str(self.y)

        if self.z >= 0:
            out = out + "+ "
        out = out + "\n z = " + str(self.z)

        return out

if __name__ == "__main__":
    """The global variable, __name__, in the module that is the entry
    point to your program, is '__main__'.
    Otherwise, it's the name you import the module by.
    So, code under the if block will only run if the module is the
    entry point to your program.
    It allows the code in the module to be importable by other modules,
    without executing the code block beneath on import.

    Notes
    -----
    Calling the class and seperate functions and printing their values.
    """

    v1 = Vector(2.8, -4.7, 0.4)
    v2 = Vector(-8.1, 3.0, -10.6)
    value1 = v1.distance(v2)
    value2 = v1.dot(v2)
    v3 = v1.cross(v2)

    print("Value1 = ", value1, "\nValue2 = ", value2, "\nv3: \n", v3)
    print("checking vector instance of v3: ", isinstance(v3, Vector))


import numpy as np

from coptim.optimizer import Optimizer

class BFGSMethod(Optimizer):
    def __init__(self):
        # TODO: More metrics: vector of x's, objective values, etc.
        self.iterations = 0

    def psi(self, t, sigma, x, d, func):
        return func.eval(x + t * d) - \
               func.eval(x) - \
               sigma * t * func.gradient(x).dot(d)

    def wolfe_powell_rule_phase_B(self, a, b, rho, sigma, x, d, func):
        a_j = a
        b_j = b
        stop = False

        while not stop:
            t_j = a_j + (b_j - a_j) / 2

            if self.psi(t_j, sigma, x, d, func) >= 0:
                b_j = t_j

            if self.psi(t_j, sigma, x, d, func) < 0 and \
                    func.gradient(x + t_j * d).dot(d) >= rho * func.gradient(x).dot(d):
                t = t_j
                stop = True

            if self.psi(t_j, sigma, x, d, func) < 0 and \
                    func.gradient(x + t_j * d).dot(d) < rho * func.gradient(x).dot(d):
                a_j = t_j

        return t

    def step_size(self, rho, sigma, x, d, func):
        gamma = 2
        t_i = 1
        stop = False
        i = 0

        while not stop:
            if self.psi(t_i, sigma, x, d, func) >= 0:
                a = 0
                b = t_i

                t_i = self.wolfe_powell_rule_phase_B(a, b, rho, sigma, x, d, func)

            if self.psi(t_i, sigma, x, d, func) < 0 and \
                    func.gradient(x + t_i * d).dot(d) >= rho * func.gradient(x).dot(d):
                t = t_i
                stop = True

            if self.psi(t_i, sigma, x, d, func) < 0 and \
                    func.gradient(x + t_i * d).dot(d) < rho * func.gradient(x).dot(d):
                i += 1
                t_i = gamma * t_i

        return t

    def optimize(self, x_0, H_0, rho, sigma, epsilon, func):
        x = x_0
        H = H_0
        while self.stopping_criteria(x, func, epsilon):
            nabla_x = -func.gradient(x)

            inverse = np.linalg.inv(H)
            d = inverse.dot(nabla_x)

            step_size = self.step_size(rho=rho,
                                       sigma=sigma,
                                       x=x,
                                       d=d,
                                       func=func)

            x_new = x + step_size * d
            s = x_new - x
            y = func.gradient(x_new) - func.gradient(x)

            H = H + (np.outer(y, y.T) / np.dot(y.T, s)) - np.outer(H.dot(s), s.T).dot(H) / (np.dot(np.dot(s.T, H), s))

            x = x_new
            self.iterations += 1

        return x

    def stopping_criteria(self, x, func, epsilon):
        return np.linalg.norm(func.gradient(x)) >= epsilon


# -*- coding: utf-8 -*-
"""
Created on Tue Aug 14 16:43:29 2018

Problem:
    If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
    Find the sum of all the multiples of 3 or 5 below 1000.

Answer: 233168

@author: tlane
"""
import numpy as np

#Generate a multiplication array
mult = np.array(range(1,350))

#Generate arrays of multiples of 3's and 5's
threes = 3 * mult
fives = 5 * mult

#Make list of multiples that are below 1000
nums = list()

for num in threes:
    if num < 1000:
        nums.append(num)

for num in fives:
    if num < 1000:
        nums.append(num)

#Sum all multiples less than 1000
nums = list(set(nums)) #removes duplicates
total = sum(nums)
print(total)


import numpy as np
from numpy.linalg import svd, norm, qr
from numpy import tensordot

def unfold(T, dim):
    """
    Unfolds a tensor T into a matrix, taking the dimension "dim" of T as the first dimension of the matrix,
    and flattening all the other dimensions into the other one dimension of the matrix.

    dim starts from 0.

    :param T: a tensor .
    :type T: numpy.ndarray
    :param dim: the dimension based on which the unfolding is made
    :type dim: int
    :returns: 2D numpy.array -- a matricisation of T.
    """
    Tm=np.moveaxis(T, dim, 0)
    return Tm.reshape(T.shape[dim],-1)

def nModeProduct(T, M, n):
    """
    n-Mode product of a tensor T  and a matrix M,  the summation is made along the nth dim.
    definition in paper "A MULTILINEAR SINGULAR VALUE DECOMPOSITION" by LIEVEN DE LATHAUWER , BART DE MOOR , AND JOOS VANDEWALLE

    For example, n with value 0, 1, or 2, would specify the 1st, 2nd or 3rd dim of the tensor T.
    For the matrix M, this function always take the second dimension, as if to multiply T by M on the left side.

    :param T: a tensor .
    :type T: numpy.ndarray
    :param M: a matrix
    :type M: numpy.array
    :param n: serial number of a dimension of T along which the summation is made.
    :type n: int
    :returns: numpy.ndarray -- a result tensor .
    """

    P=tensordot(T, M, axes=([n], [1]))
    return np.rollaxis(P, len(T.shape)-1, n)

def subTensor(T, k=None, index=None):
    """
    extract a sub-tensor t = T[:k,:k,:k,...] or t = T[index].

    :param T: a tensor .
    :type T: numpy.ndarray
    :param k: a list of integer k, indices larger than k is to be truncated. if "index" presents, this argument is overridden.
    :type k: list
    :param index: a list of list of integer indicating extracted indices on every dimension.
    :type index: list
    :returns: numpy.ndarray -- a sub-tensor .
    """
    if k.any() is None and index.any() is None:
        return T
    elif index is not None:
        return T[np.ix_(*index)]
    else:
        if isinstance(k, int): # if only one integer is assigned to k
            k=k*np.ones((len(T.shape),), dtype=int)

        index=[None]*len(k)
        for i in range(len(k)):
            index[i]=list(range(k[i]))

        return T[np.ix_(*index)]

def HOSVD(A, k=None, tol=None):
    """
    High order svd of d-dim tensor A. so that A = S (*1) u1 (*2) u2 (*3) u3 ... (*d) ud,
    "(*n)" means n-mode product. S is the core, u1,u2,u3, ... are orthogonal basis.
    definition in paper "A MULTILINEAR SINGULAR VALUE DECOMPOSITION"
    by LIEVEN DE LATHAUWER , BART DE MOOR , AND JOOS VANDEWALLE

    :param A: a full tensor .
    :type A: numpy.ndarray

    :param k: rank for the truncation.
    :type k: numpy.list of integer or a single integer

    :param tol:  error torlerance of the truncation
    :type tol: numpy.float

    :returns: numpy.ndarray -- the core tensor S,
              numpy.list    -- a list of array containing basis
    """

    d=len(A.shape)

    if d==2:
        u, s, vt=svd(A, full_matrices=False)
        U=[u, vt.T]
        S=np.diag(s)
    else:
        U=[None]*d
        for j in range(0, d):
            U[j], s, vt=svd(unfold(A, j), full_matrices=False)

        S=A.copy()
        for i in range(0, d):
            S=nModeProduct(S, U[i].T, i)

    if k is not None:
        if isinstance(k, int): # if only one integer is assigned to k
            k=k*np.ones((len(A.shape),), dtype=int)

        S=subTensor(S, k=k)
        for j in range(0, d):
            U[j]=U[j][:, :k[j]]

    return S, U

def fast_qr(A):
    """
    This is a recursive partioned QR. about two times faster than QR for tall matrices
    """
    N,M=A.shape
    if N < M:
        Q, R =qr(A)  # not tall matrices, use normal qr

    elif M>16: # the minimal partition size is 32
        R=np.zeros((M,M))

        k= np.ceil(M/2).astype(int)
        qa, R[:k, :k]=fast_qr(A[:,:k])

        R[:k,k:] = np.dot(qa.T, A[:,k:])

        qb, R[k:, k:]=fast_qr(A[:,k:]-np.dot(qa, R[:k,k:]))

        Q = np.hstack((qa, qb))
    else: # reach the smallest partition size, no more partitions, use normal qr
        Q, R =qr(A)

    return Q, R

def new_expand_dims(a, axes):
    """
    This is a new version of extend_dims() function of numpy, it extend multiple dims rather than one.
    """
    # if int is passed, retain the same behaviour
    if type(axes) == int:
        return np.expand_dims(a, axes)
    # insert axes to given indices
    for ax in sorted(axes):
        a = np.expand_dims(a, ax)
    return a

if __name__=='__main__':

    # A = np.random.rand(7,7)
    A=np.arange(49).reshape((7, 7))
    print(A)

    S, U=HOSVD(A)
    print(S)
    print(U)
    print(norm(A-np.dot(U[0], np.dot(S, U[1].T))))

    S2, U2=HOSVD(A, 5)
    print(S2)
    print(U2)
    print(norm(A-np.dot(U2[0], np.dot(S2, U2[1].T))))
#    S,U=HOSVD2(A)
#    print S
#    print U
#    print norm(A-np.dot(U[0], np.dot(S, U[1].T)))

    print('END')


import numpy as np
import matplotlib.pyplot as plt

def step_function(x):
    '''
    계단 함수
    1 (0 < x)
    0 (0 >= x)
    '''
    return np.array(x>0, dtype=np.int)

def sigmoid(x):
    '''
    시그모이드
     0~1 사이의 연속적인 값 반환
    '''
    return 1/(1+np.exp(-x))

def relu(x):
    '''
    렐루
     x (0 < x)
     0 (0 >= x)
    '''
    return np.maximum(0,x)

def identity_function(x):
    '''
    regression 출력층에 사용
     입력을 그대로 반환
    '''
    return x

def softmax(x):
    '''
    classification 출력층에 사용
     출력층의 입력 전체를 입력으로 요구함
     출력의 합은 1이 됨 -> 출력을 확률로 해석할 수 있음
    '''
    max_x = np.max(x)
    exp_x = np.exp(x - max_x)
    sum_exp_x = np.sum(exp_x)
    y = exp_x / sum_exp_x
    return y

if __name__ == '__main__':
    '''
    plot
    '''
    # x=np.arange(-5, 5, 0.1)
    # y=relu(x)
    # plt.plot(x,y)
    # plt.ylim(-0.1, 1.1)
    # plt.show()

    '''
    softmax 내용
    '''
    # a = np.array([.3, 2.9, 4.0])
    # exp_a=np.exp(a)
    # print(exp_a)
    # sum_exp_a = np.sum(exp_a)
    # print(sum_exp_a)
    # y = exp_a / sum_exp_a
    # print(y)
    # print(softmax(a))

    a = np.array([1010, 1000, 990])
    # print(np.exp(a)/np.sum(np.exp(a)))  # [ nan  nan  nan] -> RuntimeWarning: overflow encountered in exp
    c = np.max(a)   # 1010
    print(a - c)
    print(np.exp(a-c)/np.sum(np.exp(a-c)))  # overflow 해결


example = """16,1,2,0,4,2,7,1,2,14"""

import numpy as np

def preprocess (data :str)-> list:
    return [int(x) for x in data.split(',')]

def min_fuel(crabs: list) -> int:
    crabs = sorted(crabs)
    med = crabs[len(crabs)//2]
    return sum(abs(x-med) for x in crabs)

crabs = preprocess(example)
min_fuel(crabs=crabs) == 37

# Part-2
def min_fuel_2(crabs: list) -> int:
    scores = list()
    # The minimum fuel location seems to be shifting from the median to the mean as distance is more costly now (squared)
    med = np.median(crabs)
    print(f"The Median: {med}")
    avg = np.mean(crabs)
    print(f"The Average: {avg}")
    # Want to scan ~20% of the max-min range around the rounded mean value (part-2 data works with a 1% scan smallest)
    pct = 0.20
    space = (max(crabs)-min(crabs))*(pct/2)
    space = round(space)
    for x in range(int(avg)-space, int(avg)+space):
        s = 0
        for crab in crabs:
            s += abs(x-crab)*(abs(x-crab)+1)/2
        scores.append(int(s))
    print("The location for the least fuel is {}".format(int(avg)-space + np.argmin(scores)) )
    return min(scores)

assert min_fuel_2(crabs=crabs) == 168

if __name__ == '__main__':
    with open('d7.txt') as f:
        content = f.read()

    CRABS = preprocess(content)
    print("The fuel consumption for the 1st part: {} \n".format(min_fuel(crabs= CRABS)))
    print("The fuel consumption for the 2nd part: {}".format(min_fuel_2(crabs= CRABS)))


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_context('paper')
sns.set_style('whitegrid')

def newtons_method(f, fprime, guess, epsilon=1e-10, max_iter=50):
    approx = np.zeros(max_iter + 1)
    approx[0] = guess
    iterations = 0
    while iterations < max_iter:
        approx[iterations + 1] = approx[iterations] - f(approx[iterations]) / fprime(approx[iterations])
        if np.abs(approx[iterations + 1] - approx[iterations]) < epsilon:
            break
        iterations += 1

    return approx[:iterations+1], iterations

def f(x):
    return 54 * x**6 + 45 * x**5 - 102 * x**4 - 69 * x**3 + 35 * x**2 + 16 * x - 4

def fp(x):
    return 324 * x**5 + 225 * x**4 - 408 * x**3 - 207 * x**2 + 70 * x + 16

def g(x):
    const = 1 if (x-3/4) > 0 else -1
    return const * np.power(abs(1 - 3/(4*x)),1/3)

def gp(x):
    const = 1 if (x-3/4) > 0 else -1
    return const * np.power(abs(1-3/(4*x)),-2/3) * 1/(4*x**2)

x0 = 0.7500000001
approx, iterations = newtons_method(g, gp, x0)

# zero = lambda x: 0 * x
# x = np.linspace(-1.4,1.3,100)

# plt.plot(x, f(x))
# plt.plot(x, zero(x), 'k--')
# plt.xlim([-1.4,1.3])
#
# plt.title('Plot of $f(x) = 54x^6 + 45 x^5 - 102x^4 - 69x^3 + 35x^2 + 16x - 4$')
# plt.xlabel('$x$')
# plt.ylabel('$y$')
#
# plt.savefig('plot0.png', figsize=(4,4), transparent=True, bbox_inches='tight')

x = [-1.5, -0.7, 0.2, 0.5, 1.2]
results = [newtons_method(f, fp, x0) for x0 in x]

for i, x0 in enumerate(x):
    print('{:0.1f} & {:0.6f} & {:d} & {:0.6f} \\\\'.format(x0,
        results[i][0][-1],
        results[i][1],
        fp(results[i][0][-1])))

# plt.figure(figsize=(8,6))

# plt.scatter(np.arange(0,51), np.abs(approx))
#
# plt.semilogy()
# plt.xlim([0,30])
#
# plt.title('Absolute iterates of Newton\'s method for $f(x) = (1 - 3/(4x))^{1/3}$.')
# plt.ylabel('Absolute iterate approximation')
# plt.xlabel('Iteration')
#
# plt.savefig('plot.png', figsize=(4,4), transparent=True, bbox_inches='tight')


import numpy as np
from sympy import integrate, Symbol

import traj

def check_fit_seven_segment(p_start, p_end, v_max, a_max, j_max):
    t = Symbol('t')
    jerk = traj.seven_segment_type3.fit(p_start, p_end, v_max, a_max, j_max, t)
    acceleration = jerk.integrate(0.0)
    velocity = acceleration.integrate(0.0)
    position = velocity.integrate(p_start)

    t_start = position.boundaries[0]
    t_end = position.boundaries[-1]
    p_start_computed = position(t_start)
    v_start_computed = velocity(t_start)
    p_end_computed = position(t_end)[0]
    v_end_computed = velocity(t_end)[0]

    assert np.isclose(p_start, p_start_computed)
    assert np.isclose(p_end, p_end_computed)

def test_max_vel_but_not_max_acc():
    check_fit_seven_segment(0.0, 30.0, 0.1, 3.0, 0.1)

def test_max_acc_but_not_max_vel():
    check_fit_seven_segment(0.0, 30.0, 6.0, 3.0, 0.1)

def test_not_max_vel_or__max_acc():
    check_fit_seven_segment(0.0, 0.4, 3.1, 2.3, 0.1)

def test_max_vel_and_max_acc():
    check_fit_seven_segment(0.0, 30.0, 2.0, 0.4, 0.1)


from sympy import *

if __name__ == '__main__':
    w = symbols('w')
    x = symbols('x')
    y = symbols('y')

    f = w * x

    cost = 1/2*(y-f)**2
    print ('Linear Regression:', diff(cost, w))
    # Linear Regression: x*(-y + (-y + 1)*exp(w*x))/(exp(w*x) + 1)
    # Equals to: x * (y - wx)

    sigmoid = 1 / (1 + exp(-f))
    cost = -y*log(sigmoid)-(1-y)*log(1-sigmoid)
    print ('Logistic Regression:', simplify(diff(cost, w)))
    # Logistic Regression: x*(-y + (-y + 1)*exp(w*x))/(exp(w*x) + 1)
    # Equals to: x * (y - sigmoid(wx))