"""
An implementation of the Exact Fit of the first 3 Moments
(#F3M) of finding the parameters that make up the mixture
of 2 Gaussian distributions. Based on the work by
Lopez de Prado and Foreman (2014) "A mixture of two
Gaussians approach to mathematical portfolio oversight:
The EF3M algorithm." Quantitative Finance, Vol. 14,
No. 5, pp. 913-930.
"""

# imports
import numpy as np

from scipy.stats import norm, moment
from scipy.special import comb
import scipy.integrate as integrate

import pandas as pd
import dask.dataframe as dd
from dask import delayed
from dask.diagnostics import ProgressBar

# The mixture's class.
# This will contain all data and methods for determining
# the parameters of the mixture of 2 normal distributions.
class M2N:
    """
    Class used to contain parameters and equations for the
    EF3M algorithm, when fitting parameters to a mixture
    of 2 Gaussians.
    """
    def __init__(self, moments):
        """
        Constructor

        :param moments: (list) The first five (1... 5) raw moments of the
                        mixture distribution.

        The parameters of the mixture are defined by a list, where:
        parameters = [mu1, mu2, sigma1, sigma2, p1]
        """
        self.moments = moments
        # initialize the
        self.parameters = [0 for i in range(5)]  # initialize parameter list
        self.error = sum([moments[i]**2 for i in range(len(moments))])

    def fit(self, mu2, epsilon, variant=1, maxIter=100_000):
        """
        Fits and the parameters that describe the mixture
        of the 2 Normal distributions for a given set of initial
        parameter guesses.

        :param mu2: (float) An initial estimate for the mean of the second
                    distribution.
        :param epsilon: (float) Error tolerance.
        :param variant: (int) Which algorithm variant to use, 1 or 2.
        :param maxIter: (int) Maximum number of iterations after which
                        to terminate loop.
        """
        p1 = np.random.uniform(0, 1)
        numIter = 0
        while True:
            numIter += 1
            if variant == 1:
                parameters_new = self.iter4(mu2, p1, self.moments)  # first variant
            elif variant == 2:
                parameters_new = self.iter5(mu2, p1, self.moments)  # second variant
            else:
                raise ValueError("Value of 'variant' must be either 1 or 2.")
            if len(parameters_new) == 0:
                # invalid value found in iter4 or iter5
                return None
            parameters = parameters_new.copy()
            moments = self.get_moments(parameters)
            error = sum([(self.moments[i]-moments[i])**2
                        for i in range(len(moments))])
            if error < self.error:
                # update with new best parameters, error
                self.parameters = parameters
                self.error = error
            if abs(p1 - parameters[4]) < epsilon:
                # stopping condition
                break
            if numIter > maxIter:
                # maxIter reached, convergence not fast enough
                return None
            p1 = parameters[4]
            mu2 = parameters[1]  # for the 5th moments convergence
        self.parameters = parameters
        return None

    def get_moments(self, parameters):
        """
        Calculates and returns the first five (1...5) raw moments
        corresponding to the newly esitmated parameters.

        :param parameters: (list) List of parameters if the
        specific order [mu1, mu2, sigma1, sigma2, p1]
        :return: (list) List of the first five moments
        """
        u1, u2, s1, s2, p1 = parameters  # for clarity
        p2 = 1-p1  # for symmetry
        m1 = p1*u1 + p2*u2  # Eq. (6)
        m2 = p1*(s1**2 + u1**2) + p2*(s2**2 + u2**2)  # Eq. (7)
        m3 = p1*(3*s1**2*u1 + u1**3) + p2*(3*s2**2*u2 + u2**3)  # Eq. (8)
        m4 = p1*(3*s1**4 + 6*s1**2*u1**2 + u1**4) +\
            p2*(3*s2**4 + 6*s2**2*u2**2 + u2**4)  # Eq. (9)
        m5 = p1*(15*s1**4*u1 + 10*s1**2*u1**3 + u1**5) +\
            p2*(15*s2**4*u2 + 10*s2**2*u2**3 + u2**5)  # Eq. (10)
        return [m1, m2, m3, m4, m5]

    def iter4(self, mu2, p1, moments):
        """
        Evaluation of the set of equations that make up
        variant #1 of the EF3M algorithm (fitting using the
        first four moments).

        :param mu2: (float) Initial parameter value for mu2
        :param p1: (float) Probability defining the mixture; p1, 1-p1
        :param moments: (list) First five raw moments of the mixture
        distribution [m1, m2, m3, m4, m5]
        :return: (list) List of estimated parameter if no invalid values
        are encountered (e.g. complex values, divide-by-zero), otherwise
        an empty list is returned.
        """
        m1, m2, m3, m4, __ = moments  # for clarity
        # mu1, Equation (22)
        mu1 = (m1 - (1-p1)*mu2) / p1
        # sigma2, Equation (24)
        if (3*(1-p1)*(mu2-mu1)) == 0:
            # check for divide-by-zero
            return []
        sigma2_squared = ( (m3 + 2*p1*mu1**3 + (p1-1)*mu2**3 -\
                                        3*mu1*(m2 + mu2**2*(p1-1))) /\
                            (3*(1-p1)*(mu2-mu1)) )
        if sigma2_squared < 0:
            return []
        sigma2 = sigma2_squared**(.5)
        # sigma1, Equation (23)
        sigma1_squared = ( (m2 - sigma2**2 - mu2**2)/p1 +\
                                        sigma2**2 + mu2**2 - mu1**2 )
        if sigma1_squared < 0:
            return []
        sigma1 = sigma1_squared**(.5)
        if np.iscomplex(sigma1) or np.iscomplex(sigma2) or \
            np.isnan(sigma1) or np.isnan(sigma2):
            return []  # returns empty list sigma1 or sigma2 are invalid
        # adjust guess for p1, Equation (25)
        p1_deno = (3*(sigma1**4 - sigma2**4) + 6*(sigma1**2*mu1**2 - \
            sigma2**2*mu2**2) + mu1**4 - mu2**4)
        if p1_deno == 0:
            return []  # return empty list if about to divide by zero
        p1 = (m4 - 3*sigma2**4 - 6*sigma2**2*mu2**2 - mu2**4) / p1_deno
        if (p1<0) or (p1>1):
            return []
        return [mu1, mu2, sigma1, sigma2, p1]

    def iter5(self, mu2, p1, moments):
        """
        Evaluation of the set of equations that make up
        variant #2 of the EF3M algorithm (fitting using the
        first five moments).

        :param mu2: (float) Initial parameter value for mu2
        :param p1: (float) Probability defining the mixture; p1, 1-p1
        :param moments: (list) First five raw moments of the mixture
        distribution [m1, m2, m3, m4, m5]
        :return: (list) List of estimated parameter if no invalid values
        are encountered (e.g. complex values, divide-by-zero), otherwise
        an empty list is returned.
        """
        m1, m2, m3, m4, m5 = moments  # for clarity
        # mu1, Equation (22)
        mu1 = (m1 - (1-p1)*mu2) / p1
        if (3*(1-p1)*(mu2-mu1)) == 0:
            return []
        # sigma2, Equation (24)
        if (3*(1-p1)*(mu2-mu1)) == 0:
            # check for divide-by-zero
            return []
        sigma2_squared = ( (m3 + 2*p1*mu1**3 + (p1-1)*mu2**3 -\
                                        3*mu1*(m2 + mu2**2*(p1-1))) /\
                            (3*(1-p1)*(mu2-mu1)) )
        if sigma2_squared < 0:
            return []
        sigma2 = sigma2_squared**(.5)
        # sigma1, Equation (23)
        sigma1_squared = ( (m2 - sigma2**2 - mu2**2)/p1 +\
                                        sigma2**2 + mu2**2 - mu1**2 )
        if sigma1_squared < 0:
            return []
        sigma1 = sigma1_squared**(.5)
        # last check for sigma1 and sigma2 validity
        if np.iscomplex(sigma1) or np.iscomplex(sigma2) or \
            np.isnan(sigma1) or np.isnan(sigma2):
            return []
        # adjust the guess for mu2, Equation (27)
        if (1-p1) < 1e-4:
            return []
        a = ( 6*sigma2**4 + (m4-p1*(3*sigma1**4+6*sigma1**2*mu1**2+mu1**4)) /\
             (1-p1 ) )**.5
        mu2_squared = (a - 3*sigma2**2)
        if np.iscomplex(mu2_squared):
            return []
        if mu2_squared < 0:
            return []
        mu2 = mu2_squared**.5
        if np.iscomplex(mu2):
            return []
        # adjust guess for p1, Equation (28, 29)
        a = 15*sigma1**4*mu1+10*sigma1**2*mu1**3+mu1**5
        b = 15*sigma2**4*mu2+10*sigma2**2*mu2**3+mu2**5
        if (a-b) == 0:
            return []  # return empty list if about to divide by zero
        p1 = (m5-b) / (a-b)
        if (p1<0) or (p1>1):
            return []
        return [mu1, mu2, sigma1, sigma2, p1]

    def singleLoop(self, moments, epsilon=10**-5, factor=5,
                    variant=1, maxIter=100_000):
        """
        A single scan through the list of mu2 values, cataloging the
        successful fittings in a DataFrame.

        :param moments: (list) First five central moments, [m1, m2, m3, m4, m5]
        :param epsilon: (float) Fitting tolerance
        :param factor: (float) Lambda factor from equations
        :param variant: (int) The EF3M variant to execute, options
        are 1: EF3M using first 4 moments, 2: EF3M using first 5 moments
        :param maxIter: (int) Maximum number of iterations to perform
        in the 'fit' method
        :return: (pd.DataFrame) Fitted parameters and error
        """
        stDev = centeredMoment(moments, 2)**.5
        mu2 = [float(i)*epsilon*factor*stDev + moments[0]
                for i in range(1, int(1/epsilon))]
        m2n = M2N(moments)
        err_min = m2n.error
        d_results = {}
        for mu2_i in mu2:
            m2n.fit(mu2=mu2_i, epsilon=epsilon, variant=variant, maxIter=maxIter)
            if m2n.error < err_min:
                err_min = m2n.error
                d_results['mu1'], d_results['mu2'], d_results['sigma1'], \
                    d_results['sigma2'], \
                        d_results['p1'] = [[p] for p in m2n.parameters]
                d_results['error'] = [err_min]
        return pd.DataFrame.from_dict(d_results)

    # Repeat runs and collect results as a DataFrame.
    def mpFit(self, moments, epsilon=10**-5, factor=5, n_runs=1, variant=1,
                maxIter=100_000):
        """
        Parallelized implementation of 'singleLoop' method.

        :param moments: (list) First five central moments, [m1, m2, m3, m4, m5]
        :param epsilon: (float) Fitting tolerance
        :param factor: (float) Lambda factor from equations
        :param n_runs: (int) Number of times to execute 'singleLoop'
        :param variant: (int) The EF3M variant to execute, options
        are 1: EF3M using first 4 moments, 2: EF3M using first 5 moments
        :param maxIter: (int) Maximum number of iterations to perform
        in the 'fit' method
        :return: (pd.DataFrame) Fitted parameters and error
        """
        # create a list of delayed objects that return a pd.DataFrame
        dfs = [delayed(self.singleLoop)(moments=moments,
                                        epsilon=epsilon,
                                        factor=factor,
                                        variant=variant,
                                        maxIter=maxIter
                                        ) for i in range(n_runs)]
        # build a dask.DataFrame from a list of delayed objects
        ddf = dd.from_delayed(dfs)
        # compute all runs, using dask multiprocessing
        df = ddf.compute(scheduler='processes').reset_index(drop=True)
        df = df.sort_values('error')
        return df

# === Helper functions, outside class === #
def centeredMoment(moments, order):
    """
    Compute a single moment of a specific order about the mean (centered)
    given moments about the origin (raw).

    :param moments: (list) First 'order' raw moments
    :param order: (int) The order of the moment to calculate
    """
    moment_c = 0  # first centered moment is always zero
    for j in range(order + 1):
        comb = binomialCoeff(order, j)
        if j == order:
            a = 1
        else:
            a = moments[order-j-1]
        moment_c += (-1)**j*comb*moments[0]**j*a
    return moment_c

def rawMoment(central_moments, dist_mean):
    """
    Calculates a list of raw moments given a list of
    central moments.

    :param central_moments: (list) The first n (1...n) central moments as a list
    :param dist_mean: (float) The mean of the distribution
    :return: (list) The first n+1 (0...n) raw moments
    """
    raw_moments = [dist_mean]
    central_moments = [1] + central_moments  # add the zeroth moment
    for n in range(2, len(central_moments)):
        moment_n_parts = []
        for k in range(n+1):
            sum_part = comb(n, k) * central_moments[k] * dist_mean**(n-k)
            moment_n_parts.append(sum_part)
        moment_n = sum(moment_n_parts)
        raw_moments.append(moment_n)
    return raw_moments

def binomialCoeff(n, k):
    """
    Calculate the number of way 'n' things can be chosen 'k' at-a-time,
    'n'-choose-'k'. This is a simple implementation of the
    scipy.special.comb function.

    :param n: (int) The number of things
    :param k: (int) The number of things to be chosen at one time
    :return: (int) The total number of combinations
    """
    if k < 0 or k > n:
        return 0
    if k > n-k:
        k = n-k
    c = 1
    for i in range(k):
        c = c*(n - (k - (i+1)))
        c = c // (i+1)
    return c

jupyter-notebooks/hudson-and-thames-quant/Advances in Financial Machine Learning/Bet Sizing/EF3M/ef3m.py


import numpy as np

def initializeCentroids(points, k):
    """returns k centroids from the initial points"""
    centroids = points.copy()
    np.random.shuffle(centroids)
    return centroids[:k]

def closestCentroid(points, centroids):
    """returns an array containing the index to the nearest centroid for each point"""
    distances = np.sqrt(((points - centroids[:, np.newaxis])**2).sum(axis=2))
    return np.argmin(distances, axis=0)

def moveCentroids(points, closest, centroids):
    """returns the new centroids assigned from the points closest to them"""
    return np.array([points[closest == k].mean(axis=0) for k in range(centroids.shape[0])])

def evalKMeans(k, raw_data):
    points = np.array(raw_data)
    centroids = initializeCentroids(points, k)
    prev_centroids = np.zeros_like(centroids)
    for x in range(100):
        closest = closestCentroid(points, centroids)
        centroids = moveCentroids(points, closest, centroids)
        if np.linalg.norm(centroids - prev_centroids) > 0:
           prev_centroids = centroids.copy()
        else:
            break
    closest = closestCentroid(points, centroids)
    return centroids, closest


import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg
import scipy.optimize
import matplotlib.pylab as plt

def buildA(N):
    dx = 1 / N
    nvar = (N - 1)**2;
    e1 = np.ones((nvar), dtype=float);
    e2 = np.copy(e1)
    e2[:N-1:] = 0
    e3 = np.copy(e1)
    e3[N-2:N-1:] = 0
    A = sp.spdiags(
        (-e1, -e3, 4*e1, -e2, -e1),
        (-(N-1), -1, 0, 1, N-1), nvar, nvar
    )
    A = A / dx**2;
    return A

def buildf1(N):
    x = np.arange(0, 1, 1/N).reshape(N, 1)
    y = x.T
    f = np.dot(np.sin(np.pi*x), np.sin(np.pi*y))
    return f[1:,1:].reshape(-1,1)

def buildf2(N):
    x = np.arange(0, 1, 1/N).reshape(N, 1)
    y = x.T
    f = np.dot(np.maximum(x,1-x), np.maximum(y,1-y))
    return f[1:,1:].reshape(-1, 1)

def jacobi(A, b, x0=None, tol=1e-5, max_iter=1000):
    if x0 is None:
        x0 = np.zeros_like(b)
    x = np.copy(x0)
    b_norm = np.linalg.norm(b)

    # jacobi method: M = D
    M = A.diagonal().reshape(-1, 1)
    invM = 1/M

    # main relaxation iteration
    for i in range(max_iter):
        r = b - A @ x
        error = np.linalg.norm(r) / b_norm
        if error < tol:
            break
        x += invM * r
    return x, i

def SOR(A, b, omega, x0=None, tol=1e-5, max_iter=300):
    if x0 is None:
        x0 = np.zeros_like(b)
    x = np.copy(x0)
    b_norm = np.linalg.norm(b)

    # SOR method
    D = sp.spdiags((A.diagonal()), (0), *A.shape)
    L = sp.tril(A, k=-1)
    M = (1/omega) * D + L

    # main relaxation iteration
    for i in range(max_iter):
        r = b - A @ x
        error = np.linalg.norm(r) / b_norm
        if error < tol:
            break
        x += sp.linalg.spsolve_triangular(M, r)
    return x, i

num = 20
iterations = np.empty((num, 2), dtype=int)
iterations[:] = np.nan
Ns = np.logspace(0.5, 1.5, num=num, dtype=int)
for j, buildf in enumerate((buildf1, buildf2)):
    for i, N in enumerate(Ns):
        A = buildA(N)
        f = buildf(N)
        max_iter = 10*N
        x, iters = jacobi(A, f, max_iter=max_iter)
        if i < max_iter:
            iterations[i, j] = iters

plt.plot(Ns, iterations)
plt.xlabel('N')
plt.ylabel('iterations')
plt.show()

N = 64
A = buildA(N)
f = buildf2(N)

def SOR_iterations(omega):
    x, i = SOR(A, f, omega, max_iter=10, tol=1e-32)
    return np.linalg.norm(A @ x - f)

res = scipy.optimize.minimize_scalar(SOR_iterations, bracket=[0.1, 1.0, 1.99], tol=1e-2)
print('ideal omega is', res.x, 'versus analytic value of', 2 / (1 + np.sin(np.pi/N)))


import numpy as np

np.__version__

# make list from 0-9
L = list(range(10))
L
type(L[0])

# Use list comprehension to cast ints as strings
L2 = [str(c) for c in L]
L2
type(L2[0])

# Creating Arrays from Python Lists
# integer array:
np.array([1,4,2,5,2])

# np.arrays can only contain one type of info, so if there is
# a mix of types it will try to typecast them into a single one
np.array([3.14, 2, 3, 4]) # will result in all being floats

# you can also be quite explicit
np.array([1, 2, 3, 4], dtype='float32')

# finally, unlike python lists, numpy arrays can explicitly be multidimensional:
# nested lists results in multidimensional arrays
np.array([range(i,i+3) for i in [2,4,6]]) # nb the inner lists, are treated as rows of the resulting two_dimesnional array

# Creating Arrays from scratch
## for larger arrays it is more efficient to create arrays from scratch using routines built into Numpy

# Create a length-10 integer array filled with zeros
np.zeros(10, dtype=int)

# Create a 3x5 floating-point array filled with ones
np.ones((3, 5), dtype=float)

# Create a 3x5 array filled with 3.14
np.full((3, 5), 3.14)

# Create an array filled with a linear sequence
# Starting at 0, ending at 20, stepping by 2
# (this is similar to the built-in range() function)
np.arange(0, 20, 2)

# Create an array of five values evenly spaced between 0 and 1
np.linspace(0, 1, 5)

# Create a 3x3 array of uniformly distributed
# random values between 0 and 1
np.random.random((3, 3))

# Create a 3x3 array of normally distributed random values
# with mean 0 and standard deviation 1
np.random.normal(0, 1, (3, 3))

# Create a 3x3 array of random integers in the interval [0, 10)
np.random.randint(0, 10, (3, 3))

# Create a 3x3 identity matrix
np.eye(3)

# Create an uninitialized array of three integers
# The values will be whatever happens to already exist at that memory location
np.empty(3)

# The standard NumPy data types are listed in the following table.
# Note that when constructing an array, they can be specified using a string:
np.zeros(10, dtype='int16')
# or using the associated NumPy object:
np.zeros(10, dtype=np.int16)
# these are the same


# -*- coding: utf-8 -*-
"""
Created on Fri Mar  1 18:19:41 2019

@author: Ham

HackerRanch Challenge: Mean, Var and Std

mean

The mean tool computes the arithmetic mean along the specified axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.mean(my_array, axis = 0)        #Output : [ 2.  3.]
print numpy.mean(my_array, axis = 1)        #Output : [ 1.5  3.5]
print numpy.mean(my_array, axis = None)     #Output : 2.5
print numpy.mean(my_array)                  #Output : 2.5
By default, the axis is None.
Therefore, it computes the mean of the flattened array.

var

The var tool computes the arithmetic variance along the specified axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.var(my_array, axis = 0)         #Output : [ 1.  1.]
print numpy.var(my_array, axis = 1)         #Output : [ 0.25  0.25]
print numpy.var(my_array, axis = None)      #Output : 1.25
print numpy.var(my_array)                   #Output : 1.25
By default, the axis is None.
Therefore, it computes the variance of the flattened array.

std

The std tool computes the arithmetic standard deviation along the specified axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.std(my_array, axis = 0)         #Output : [ 1.  1.]
print numpy.std(my_array, axis = 1)         #Output : [ 0.5  0.5]
print numpy.std(my_array, axis = None)      #Output : 1.11803398875
print numpy.std(my_array)                   #Output : 1.11803398875
By default, the axis is None.
Therefore, it computes the standard deviation of the flattened array.

Task

You are given a 2-D array of size NxM.
Your task is to find:

The mean along axis 1
The var along axis 0
The std along axis None

Input Format

The first line contains the space separated values of N and M.
The next N lines contains N space separated integers.

Output Format

First, print the mean.
Second, print the var.
Third, print the std.

Sample Input

2 2
1 2
3 4

Sample Output

[ 1.5  3.5]
[ 1.  1.]
1.11803398875

"""

import numpy

# ignoring 2nd int of 1st line, M
a = [list(map(int, input().strip().split()))
     for _ in range(int(input().strip().split()[0]))]
# I cheated: got the printoptions from Discussion
numpy.set_printoptions(legacy='1.13')
print(numpy.mean(a, axis=1))
print(numpy.var(a, axis=0))
print(numpy.std(a))


# 2 DOF SYSTEM
import numpy as np
import matplotlib.pyplot as plt
import libraryTugas as lib

# 1. SYSTEMS PARAMETERS
#================
# a. Initial condition Block 1 & 2
x_init1, xDot_init1 =  0.1, 0   # [m], [m/s]
x_init2, xDot_init2 = -0.1, 0   # [m], [m/s]

# b. System parameters Block 1 & 2
mass1, damp1, spring1 = 1, 1, 10 # [kg], [Ns/m], [N/m]
mass2, damp2, spring2 = 2, 1, 20 # [kg], [Ns/m], [N/m]

# c. Time parameters
timeStart, timeStop, stepTime = 0, 10, 0.001 # [S]

# d. Define System MODEL!
def systemFunction (y, t): # Fungsi persamaan gerak
    Qe1 = -spring1*float(y[0]) + spring2*(float(y[1])-float(y[0]))- damp1*float(y[2]) + damp2*(float(y[3])-float(y[2]))
    Qe2 = -spring2*(float(y[1])-float(y[0])) - damp2*(float(y[3])-float(y[2]))
    externalForces_Matrix = np.array([[Qe1], [Qe2]], dtype = float)
    xDotDot = np.dot(mass_MatInverse, externalForces_Matrix)
    # dalam kasus ini, parameter "t" tidak terpakai
    return xDotDot

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

# 2. INITIALIZE SIMULATION
# a. Simulation time
time = np.arange(timeStart, timeStop, stepTime, dtype = float)
# b. Mass matrix
mass_Matrix = np.array([[mass1, 0],
                        [0, mass2]], dtype = float)
mass_MatInverse = np.linalg.inv(mass_Matrix)

# c. Initial Condition of THE state
y = np.array([[x_init1], [x_init2], [xDot_init1], [xDot_init2]], dtype = float)

# 3. SOLVING EQUATION OF MOTION USING RUNGE KUTTA 4th ORDER!!
position, velocity, acceleration = lib.rungeKutta4(y, time,
                                                systemFunction, stepTime)

# 4. PLOTING RESULTS!!
plt.figure(1)
plt.plot(time, position[:,0:1])
plt.plot(time, position[:,1:2])
title = "position plot [step time = %1.6f s]" % stepTime
plt.title(title)
plt.ylabel('displacement [m]')
plt.xlabel('time [s]')
plt.grid(True)
plt.legend(["mass 1", "mass 2"])

plt.figure(2)
plt.plot(time, velocity[:,0:1])
plt.plot(time, velocity[:,1:2])
title = "velocity plot [step time = %1.6f s]" % stepTime
plt.title(title)
plt.ylabel('velocity [m/s]')
plt.xlabel('time [s]')
plt.grid(True)
plt.legend(["mass 1", "mass 2"])

plt.figure(3)
plt.plot(time, acceleration[:,0:1])
plt.plot(time, acceleration[:,1:2])
title = "acceleration plot [step time = %1.6f s]" % stepTime
plt.title(title)
plt.ylabel('acceleration [m/s/s]')
plt.xlabel('time [s]')
plt.grid(True)
plt.legend(["mass 1", "mass 2"])
plt.show()


from __future__ import division
# from math import sin, cos
import pylab as pl
import numpy as np
from numpy import sin,cos

def f_dash_central_diff(f,x,h):
    return (f(x + h) - f(x - h)) / (2 * h)

def calculate():
    x = 0.5
    h_initial = 0.5
    h = np.asarray([h_initial/(4 ** _) for _ in range(25)])

    f = sin
    f_dash = cos
    f_3dash = lambda x: -1 * cos(x)

    error_total = abs(f_dash_central_diff(f,x,h) - f_dash(x))
    error_trunc = abs((-1 * (h ** 2)/6) * f_3dash(x))
    error_round = abs(error_total - error_trunc)

    print(h, error_total,  error_trunc, error_round)
    pl.loglog(h, error_total, h, error_trunc, h, error_round)
    pl.legend(["$\epsilon$", "$\epsilon_t$", "$\epsilon_r$"], loc = 4)
    pl.savefig("solution1_fig.pdf")
    pl.show()

if __name__ == '__main__':
    calculate()


'''
want to do a quick demo of astrologer example in agresti statistics book

source: Agresti-Statistics book, ch9, pg 434 (pdf number)
"can astrologers predict personality better than random guessing?"

NOTE: typically, to disprove the null hypothesis (usually some sort of negative
    / disbelief prediction stating that something is NOT true), a p-value
    (probability value) with a "significance level" of 0.05 or lower is desired,
    which simply means that given the sample results, there's a 95% chance that
    the null hypothesis is wrong beyond a reasonable doubt.

'''

import numpy as np
import scipy.stats as st

''' in this example, the values are as such:
there were 116 attempts, n=116
astrologers had to choose one of three options, so p0 = 1/3 (random guess)
astrologers guessed correctly 40 times
null hypothesis: astrologers are no better than guessing

'''

p0 = 1/3
n=116
nCorrect = 40
p = nCorrect / n
stdErr0 = (p0*(1-p0)/n)**0.5 # standard error if p0 is correct, must be disproven
zstat = (p-p0)/stdErr0
pvalue = st.t.sf(zstat,n-1)
# pvalue is the "survival function" (1-tail distribution) of the student
#   distribution, given zvalue and degrees of freedom
print('astronomers got {} correct, zstat {}, and pvalue {}'.format(nCorrect,zstat,pvalue))

# given the original numbers, pvalue is 0.396, meaning that the null hypothesis
# is still valid, aka astronomers cannot be assumed to do better than random
# guessing. they would have needed to get 48 guesses correct at minimum.


from sympy import symbols, solve, diff, pprint, integrate

# After a long study, tree scientists conclude that a eucalyptus tree will grow at the rate of

t = symbols( 't' )
R = 0.5 + 4 / ( t + 1 ) **3
#  feet per year, where t is the time (in years).
pprint( R )

# Find the number of feet that the tree will grow in the second year.
a, b = 1, 2
round( integrate( R, ( t, a, b ) ), 3 ) # Round to three decimal places as needed.

# Find the number of feet that the tree will grow in the second year.
a, b = 2, 3
round( integrate( R, ( t, a, b ) ), 3 ) # Round to three decimal places as needed.


"""
Linear least-squares fit.
Following the procedure oulined in Philip R. Bevington, "Data Reduction and
Error Analysis for the Physical Sciences" (Third Edition, 2003),
Chapter 7.2, "Least Squares Fit to a Polynomial - Matrix Solution"

Friedrich Schotte, 20 May 2008 - 27 Apr 2012
"""

__version__ = "1.3.3"

def linear_fit(y,X):
    """Find the optimal solution to the equation y = a*X
    (Minimize the sum of the squares of the elements of y - a*X).
    Return a*X.
    y is usually used to pass experimental data.
    X is used to pass the fit model. It can be a a set of base functions,
    evaluated at the point for which the experimental data y is available.
    X has to be a matrix, y and a can be both vectors or both matrices.
    """
    from numpy import dot,nan

    # Handle degenerate case by returning NaN rather than throwing an exception.
    if X.shape[0] == 0: return y+nan

    a = linear_fit_coeff(y,X)
    fit = dot(a,X)
    return fit

def linear_fit_coeff(y,X):
    """Find the optimal solution to the equation y = a*X
    (Minimize the sum of the squares of the elements of y - a*X).
    Return a.
    y is usually used to pass experimental data.
    X is used to pass the fit model. It can be a a set of base functions,
    evaluated at the point for which the experimental data y is available.
    X has to be a matrix.
    If y is a vector, the returned coefficients a are a vector.
    If y is a matrix, the returned coefficients a are a matrix, too.
    """
    from numpy import nan,dot
    from numpy.linalg import inv

    alpha = dot(X,X.T)
    beta = dot(X,y.T)
    try: epsilon = inv(alpha)
    except: epsilon = alpha*nan
    a = dot(epsilon,beta)
    return a.T

def weighted_linear_fit(y,w,X):
    """Find the optimal solution to the equation y = a*X
    (Minimize the sum of the squares of the elements of y - a*X).
    Return a*X.
    y is usually used to pass experimental data.
    w is the weight for each element of y (recommended: 1/sigma**2)
    X is used to pass the fit model. It can be a a set of base functions,
    evaluated at the point for which the experimental data y is available.
    X has to be a matrix, y and a can be both vectors or both matrices.
    """
    from numpy import dot,nan

    # Handle degenerate case by returning NaN rather than throwing an exception.
    if X.shape[0] == 0: return y+nan

    a = weighted_linear_fit_coeff(y,w,X)
    fit = dot(a,X)
    return fit

def weighted_linear_fit_coeff(y,w,X):
    """Find the optimal solution to the equation y = a*X
    (Minimize the sum of the squares of the elements of y - a*X).
    Return a.
    y is usually used to pass experimental data.
    w is the weight for each element of y (recommended: 1/sigma**2)
    X is used to pass the fit model. It can be a a set of base functions,
    evaluated at the point for which the experimental data y is available.
    X has to be a matrix.
    If y and sigma is are vectors, the returned coefficients a are a vector.
    If y and sigma is are matrices, the returned coefficients a are a matrix.
    """
    from numpy import where,isnan,nan,dot,array
    from numpy.linalg import inv

    # 'y's that are NaNs should have zero weight.
    w = where(isnan(y),0,w)
    y = where(isnan(y),0,y)
    # Ignore NaNs in 'y' if weight is zero.
    y = where((w==0) & isnan(y),0,y)

    if y.ndim == 1:
        alpha = dot(w * X,X.T)
        beta = dot(X,(w * y).T)
        try: epsilon = inv(alpha)
        except: epsilon = alpha*nan
        a = dot(epsilon,beta)
        return a.T
    else:
        return array([weighted_linear_fit_coeff(y[i],w[i],X)
            for i in range(0,len(y))])

if __name__ == "__main__": # Example for testing
    from time import clock
    from numpy import *

    N = 5
    x = arange(0.0,2.001*pi,2*pi/(N-1))
    y1 = 1 + 0.1*sin(x) + 0.01*cos(2*x)
    y2 = 2 + 0.2*sin(x) + 0.02*cos(2*x)
    y = array([y1,y2])
    sigma = sqrt(y)

    # Basis vectors
    X0 = 1 + 0*x
    X1 = sin(x)
    X2 = cos(2*x)
    X = array([X0,X1,X2])

    ##fit = linear_fit(y,X)
    fit = weighted_linear_fit(y,1/sigma**2,X)
    print "expecting:",y[:5]
    print "result:",fit[:5]
    print "fit residual RMS",std(y-fit)
    ##a = linear_fit_coeff(y,X)
    a = weighted_linear_fit_coeff(y,1/sigma**2,X)
    print "fit residual RMS",std(y - matrix(a)*matrix(X))


#! /usr/bin/python

#%%
import numpy as np
from scipy.linalg import null_space
from scipy.linalg import lu
from sympy import Matrix
import pprint
A = np.array([
    [1, 2],
    [3, 6]
    ])
ns_a = null_space(A)
print(ns_a)
#%%
B = np.array([
    [1, 2, 3]
    ])
ns_b = null_space(B)
print(ns_b)

mx = Matrix(A)
print(mx.rref())

P, L, U = lu(A)
pprint.pprint(L)
pprint.pprint(U)
pprint.pprint(P)
pprint.pprint(P@L@U)


#setup: N = 10000 ; import numpy as np ; np.random.seed(0); t0, p0, t1, p1 = np.random.randn(N), np.random.randn(N), np.random.randn(N), np.random.randn(N)
#run: arc_distance(t0, p0, t1, p1)

#pythran export arc_distance(float64 [], float64[], float64[], float64[])

import numpy as np
def arc_distance(theta_1, phi_1,
                       theta_2, phi_2):
    """
    Calculates the pairwise arc distance between all points in vector a and b.
    """
    temp = np.sin((theta_2-theta_1)/2)**2+np.cos(theta_1)*np.cos(theta_2)*np.sin((phi_2-phi_1)/2)**2
    distance_matrix = 2 * (np.arctan2(np.sqrt(temp),np.sqrt(1-temp)))
    return distance_matrix


# Importing numpy to calculate mean, standard deviation, get arrays from numpy array i.e. matrix columns
import numpy as np
# importing csv module to read the given input csv file
import csv
# Opening file in read mode
input_csv=open("input2.csv","r")
# Getting the input csv text content
input_csv_text=csv.reader(input_csv)
# Converting csv object into list
input_data = list(input_csv_text)
# Given alpha values and +1 free value i.e. 0.8
alpha_vals =  [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10, 0.8]
# Converting inpur list of lists to numpy array
input_data_arr = np.array(input_data)
# Retrieving label - y height
y_height = input_data_arr[:,2]
# Converting string to float using list comprehensions
y_height = [float(el) for el in y_height]
# Retrieving feature - x age
x_age = input_data_arr[:,0]
x_age = [float(el) for el in x_age]
# Calculating mean and standard deviataion to get the scaled values of features
x_age_mean = np.mean(x_age)
x_age_std = np.std(x_age)
# Calculating scaled values
x_age_scaled = [(el - x_age_mean)/x_age_std for el in x_age]
# Performing same as above for feature - x weight
x_weight = input_data_arr[:,1]
x_weight = [float(el) for el in x_weight]
x_weight_mean = np.mean(x_weight)
x_weight_std = np.std(x_weight)
x_weight_scaled = [(el - x_weight_mean)/x_weight_std for el in x_weight]
print_list = []
# Looping through alpha vals
for alpha in alpha_vals:
    # initializing intercept b_0, other weights to 0
    b_0, b_age, b_weight = 0, 0, 0
    # Iterating for 100 times
    for _ in range(100):
        # Adjusting values of weights as per the formula.
        common_list = [(b_0 + b_age *x_a + b_weight * x_w - y_h)  for x_a, x_w, y_h in zip(x_age_scaled, x_weight_scaled, y_height)]
        b_0 += -(alpha / len(x_weight_scaled)) * sum(common_list)
        b_age += -(alpha / len(x_weight_scaled)) * sum([cel*x_a for cel, x_a in zip(common_list, x_age_scaled)])
        b_weight += -(alpha / len(x_weight_scaled)) * sum([cel*x_w for cel, x_w in zip(common_list, x_weight_scaled)])
    # Adding weights to print list
    print_list.append([alpha, 100, b_0, b_age, b_weight])
input_csv.close()
# Writing the weights to output csv file.
output_csv=open("output2.csv","w")
for el in print_list:
    output_csv.write(",".join([str(ind) for ind in el]))
    output_csv.write("\n")
output_csv.close()


from abc import abstractmethod
from math import exp

import numpy as np

class ActivationFunction:
    def __init__(self):
        pass

    @staticmethod
    def determine_function(func_str):
        return_function = None
        if func_str.lower() == 'sigmoid':
            return_function = SigmoidFunction()
        elif func_str.lower() == 'tanh':
            return_function = TanhFunction()
        elif func_str.lower() == 'softmax':
            return_function = SoftmaxFunction()

        return return_function

    @abstractmethod
    def activate(self, value):
        pass

class SigmoidFunction(ActivationFunction):
    def __init__(self):
        super().__init__()

    def activate(self, val):
        """
            https://machinelearningmastery.com/choose-an-activation-function-for-deep-learning/
            :param val:
            :return:
        """
        return 1.0 / (1.0 + exp(-val))

class TanhFunction(ActivationFunction):
    def __init__(self):
        super().__init__()

    def activate(self, val):
        """
            https://machinelearningmastery.com/choose-an-activation-function-for-deep-learning/
            :param val:
            :return:
                """
        return (exp(val) - exp(-val)) / (exp(val) + exp(-val))

class SoftmaxFunction(ActivationFunction):
    def __init__(self):
        super().__init__()

    def activate(self, val):
        """
            https://medium.com/data-science-bootcamp/understand-the-softmax-function-in-minutes-f3a59641e86d
            Compute softmax values for each sets of scores in x.
        """
        return np.exp(val) / np.sum(np.exp(val), axis=0)


from math import sqrt
import numpy as np

def rms_error(base_curve, fitted_curve):
    error = base_curve - fitted_curve
    return np.sqrt(np.mean(error ** 2))

def nrms_error(base_curve, fitted_curve, method="mean"):

    if method == "mean":
        Norm = np.mean(base_curve)
    elif method == "range":
        Norm = np.max(base_curve) - np.min(base_curve)
    elif method == "std":
        Norm = np.std(base_curve)
    elif method == "absmax":
        Norm = np.max((abs(np.max(base_curve)), abs(np.min(base_curve))))

    if Norm != 0.0:
        error = base_curve - fitted_curve
        return np.sqrt(np.mean((error / Norm) ** 2))

    else:
        raise NotImplementedError

def mae_error(base_curve, fitted_curve):
    abs_error = np.abs(base_curve - fitted_curve)
    return np.mean(abs_error)

def L2_norm(vector):
    return np.sqrt(np.sum(vector ** 2.0))

def p_norm(vector, p):

    if p < 1.0:
        print(" p must be greater or equal to 1. Stopping...")
        exit()

    return np.sum(np.abs(vector) ** p) ** (1.0 / p)

def mape_error(base_curve, fitted_curve):
    """	The mean absolute percentage error (MAPE)"""
    if 0 not in base_curve:
        Normed_error = (base_curve - fitted_curve) / base_curve
        return np.mean(Normed_error)
    else:
        raise ValueError


print("-----------------LU-Decomposition-------------------")

import numpy as np
'''

###Important note:::: when you change the matrix from another matrix then also change 'c'(which is 'b') vector
#which is defined later, after creating upper triangular matrix.

a=np.array([[20,15,10],
            [-3,-2.249,7],
            [5,1,3]],float)
b=np.array([45,1.751,9],float)
'''
a=np.array([[25,5,1],
            [64,8,1],
            [144,12,1]],float)
b=np.array([106.8,177.2,279.2],float)

n =len(b)
z=np.zeros(n,float)
x= np.zeros(n,float)  ## solution vector
L=np.eye(n,n)  ## creating identity matrix
# print(L)

### Partial Pivoting
for k in range(n-1):
    if abs(a[k,k])<1.0e-10:
        for j in range(k+1,n):
            if abs(a[j,k])>abs(a[k,k]):
                a[[j,k]]=a[[k,j]]
                b[[j,k]]=b[[k,j]]
                break

#Elimination:

            ## creating upper(U) and lower triangular(L) matrices----------->
    for i in range(k+1,n):
        if a[i,k]==0:
            continue

        factor = a[i,k]/a[k,k] ## creating factor so that we can create a upper triangular matrix
        L[i,k]=factor  ## creating lower triangular matrix

        for j in range(k,n):
            a[i,j]= a[i,j]- a[k,j]*factor
        # b[i]=b[k]-b[i]*factor
## upper triangular matrix U
#print("--------------------------------------------")
U=a

print("Upper triangular matrix is U: ")
print(U)

print("--------------------------------------------")
print("Lower Triangular matrix is L: ")
print(L)

c=np.array([106.8,177.2,279.2],float)
print("\n")
## here I am creating the same vector b with
#  different name because we changed above the b vector.
print("--------------------------------------------")

### upper triangular matrix
## Forward Substitution
z[0]=b[0]/L[0,0]  ## or z[0]=b[0] because L[0,0]=1
# print(z)
for i in range(1,n):
    sum_Lz=0
    for m in range(0,i):
        sum_Lz+=L[i,m]*z[m]
        # print(sum_Lz)
    z[i]=(c[i]-sum_Lz)/L[i,i]  ##L[i,i]=1
print("z vector from forward substitution: ")
print("z := ",z)

print("\n")
print("------------------Solution vector from LU-Decomposition--------------------------")

## Back-Substitution on upper triangular matrix to get solution vector
## here z vector will be used as constant Right hand vector
x[n-1]= z[n-1]/U[n-1,n-1]
for i in range(n-2,-1,-1):
    sum_Ux=0
    for j in range(i+1,n):
        sum_Ux+=U[i,j]*x[j]
    x[i]=(z[i]-sum_Ux)/U[i,i]

print("\n")

print("The value of solution vector is: ")
print("x := ", x)

##we can compare this result with Gauss-Elimination method

print("\n")

print("------------------ Comparison with Gauss-Elimination Method-----------------\n")

a=np.array([[25,5,1],
        [64,8,1],
        [144,12,1]],float)
b=np.array([106.8,177,279.2],float)
## length of the vector
n= len(b)
# defining zeros to fill the entries of x
x= np.zeros(n,float)
##

## --------------Partial Pivoting---------------

for k in range(n-1):
    if abs(a[k,k])<1.0e-10:
        for i in range(k+1, n):  ### I have to check here what if i take n instead of (n-1), can we interchange the pivot row with the last row.
            if abs(a[i,k])> abs(a[k,k]):### it doesn't matter whether we take n or n-1in the previous range
                a[[i,k]]=a[[k,i]]
                b[[k,i]]=b[[i,k]]
                break

    # Elimination---->
### note: here I am using different  factor than I used in gauss Elimination method (factor= a[i,k]/a[k,k])
    #for k in range(n-1):
    for i in range(k+1,n):
        if a[i,k]==0:
            continue
        factor= a[i,k]/a[k,k]
        for j in range(k,n):
            a[i,j]= a[i,j]- a[k,j]*factor
        b[i]=b[i]-b[k]*factor
# print(a)
## back-Substitution---->

x[n-1]= b[n-1]/a[n-1,n-1]
for i in range(n-2,-1,-1):
    sum_ax=0
    for j in range(i+1,n):
        sum_ax+=a[i,j]*x[j]
    x[i]= (b[i]-sum_ax)/a[i,i]

print("x:= ", x)


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

import numpy as np

def get_e(n):
    e = np.zeros((1,n))
    e[0,0] = 1
    return e

def get_alfa(A,j):
    alfa = np.array([])
    for i in range (1,len(A),1):
        alfa = np.append(alfa,A[i,j])
    return alfa

def get_delta(A):
    return (A[1,0]/abs(A[1,0]))

def norm(A):
    x=0
    for i in range (len(A)):
        x=x+(A[i]**2)
    x = np.sqrt(x)
    return x

def get_wi(A):
    ai = np.array([])
    ai = np.append(ai,get_alfa(A,0))
    n = len(ai)
    wi = ai+(get_delta(A)*norm(ai)*get_e(n))
    return wi

def householder_algorithm(A,debug = False):
    get_hwi_x = lambda w,x: (x - (2*(np.inner(w,x)/np.inner(w,w))*w))
    A = A.copy()
    n = len(A)
    new_A = np.zeros((n,n))
    for i in range(0,n-2):
        m = len(A)
        wi = get_wi(A)
        for j in range(0,m):
            A[1:m,j] = get_hwi_x(wi,get_alfa(A,j))
        for j in range(0,m):
            A[j,1:m] = get_hwi_x(wi,get_alfa(np.transpose(A),j))
        new_A[i:n,i:n] = A
        A = np.delete(A,0,0)
        A = np.delete(A,0,1)
    new_A[n-2:n,n-2:n] = A
    if(debug==True):
        print("new_A = \n",np.matrix.round(new_A,4),"\n")
    return new_A

def test_wi(A):
    alfa = get_alfa(A,1)
    wi = get_wi(A)
    y = alfa - (2*(np.inner(wi,alfa)/np.inner(wi,wi))*wi)
    print("wi =",y)

def test_householder_algorithm():
    A = np.array([[2,-1,1,3],
                  [-1,1,4,2],
                  [1,4,2,-1],
                  [3,2,-1,1]], dtype=float)
    householder_algorithm(A,debug=True)

def main():
    print("Execução da função test_wi(A)")
    A = np.array([[2,-1,1,3],
                  [-1,1,4,2],
                  [1,4,2,-1],
                  [3,2,-1,1]], dtype=float)
    test_wi(A)
    print("\n Execução da função test_householder_algorithm()")
    test_householder_algorithm()

if __name__ == "__main__":
    try:
        main()

    except KeyboardInterrupt:
        print("\n Better luck next time")


"""Symmetric projection operator."""
from itertools import permutations

import numpy as np
import scipy

from toqito.perms import permutation_operator

def symmetric_projection(
    dim: int, p_val: int = 2, partial: bool = False
) -> [np.ndarray, scipy.sparse.lil_matrix]:
    r"""
    Produce the projection onto the symmetric subspace [CJKLZ14]_.

    For a complex Euclidean space :math:`\mathcal{X}` and a positive integer :math:`n`, the
    projection onto the symmetric subspace is given by

    .. math::
        \frac{1}{n!} \sum_{\pi \in S_n} W_{\pi}

    where :math:`W_{\pi}` is the swap operator and where :math:`S_n` is the symmetric group on
    :math:`n` symbols.

    Produces the orthogonal projection onto the symmetric subspace of :code:`p_val` copies of
    `dim`-dimensional space. If `partial = True`, then the symmetric projection (PS) isn't the
    orthogonal projection itself, but rather a matrix whose columns form an orthonormal basis for
    the symmetric subspace (and hence the PS * PS' is the orthogonal projection onto the symmetric
    subspace).

    This function was adapted from the QETLAB package.

    Examples
    ==========

    The :math:`2`-dimensional symmetric projection with :math:`p=1` is given as
    :math:`2`-by-:math:`2` identity matrix

    .. math::
        \begin{pmatrix}
            1 & 0 \\
            0 & 1
        \end{pmatrix}.

    Using :code:`toqito`, we can see this gives the proper result.

    >>> from toqito.perms import symmetric_projection
    >>> symmetric_projection(2, 1).todense()
    [[1., 0.],
     [0., 1.]]

    When :math:`d = 2` and :math:`p = 2` we have that

    .. math::
        \begin{pmatrix}
            1 & 0 & 0 & 0 \\
            0 & 1/2 & 1/2 & 0 \\
            0 & 1/2 & 1/2 & 0 \\
            0 & 0 & 0 & 1
        \end{pmatrix}.

    Using :code:`toqito` we can see this gives the proper result.

    >>> from toqito.perms import symmetric_projection
    >>> symmetric_projection(dim=2).todense()
    [[1. , 0. , 0. , 0. ],
     [0. , 0.5, 0.5, 0. ],
     [0. , 0.5, 0.5, 0. ],
     [0. , 0. , 0. , 1. ]]

    References
    ==========
     .. [CJKLZ14] J. Chen, Z. Ji, D. Kribs, N. Lütkenhaus, and B. Zeng.
        "Symmetric extension of two-qubit states".
        Physical Review A 90.3 (2014): 032318.
        https://arxiv.org/abs/1310.3530
        E-print: arXiv:1310.3530 [quant-ph]

    :param dim: The dimension of the local systems.
    :param p_val: Default value of 2.
    :param partial: Default value of 0.
    :return: Projection onto the symmetric subspace.
    """
    dimp = dim ** p_val

    if p_val == 1:
        return np.eye(dim)

    p_list = np.array(list(permutations(np.arange(1, p_val + 1))))
    p_fac = np.math.factorial(p_val)
    sym_proj = np.zeros((dimp, dimp))

    for j in range(p_fac):
        sym_proj += permutation_operator(dim * np.ones(p_val), p_list[j, :], False, True)
    sym_proj = sym_proj / p_fac

    if partial:
        sym_proj = scipy.linalg.orth(sym_proj)
    return sym_proj


from sympy import *

x = symbols('x')
init_printing(use_unicode=True)

f=Lambda(x, x*cos(x)-x**2*sin(x))
g=Lambda(x, diff(f(x),x))

# Progresiva
#f'(c)= (-3f(c)+4f(c+h)-f(c+2h))/2h
def fdn1(c, h, valores):
    return (-3*valores[c]+4*valores[c+h]-valores[c+2*h])/(2*h)
    #return (-3*valores[round(c,1)]+4*valores[round(c+h,1)]-valores[round(c+2*h,1)])/(2*h)

# Central
#f'(c)= (f(c+h)-f(fc-h))/2h
def fdn2(c, h, valores):
    return (valores[c+h]-valores[c-h])/(2*h)

# Regresiva
#f'(c)= (-f(c-2h)+4f(c-h)-3f(c))/2h
def fdn3(c, h, valores):
    return (valores[c-2*h]-4*valores[c-h]+3*valores[c])/(2*h)
    #return (valores[round(c-2*h,1)]-4*valores[round(c-h,1)]+3*valores[round(c)])/(2*h)

valores=dict({(2.9,-4.827866), (3.0,-4.240058), (3.1,-3.496909), (3.2,-2.596792)})

h=0.1
a=2.9

c=a+0*h
print("c=",c)

print("Progresiva:")
fdn1c=fdn1(c, h, valores)
print("FDN1: f'(c)=", fdn1c)
print("Error FDN1:", abs(fdn1c-g(c)))

c=a+1*h
print("c=",c)

print("Progresiva:")
fdn1c=fdn1(c, h, valores)
print("FDN1: f'("+str(c)+")=", fdn1c)
print("Error FDN1:", abs(fdn1c-g(c)))

print("Central:")
fdn2c=fdn2(c, h, valores)
print("FDN2: f'("+str(c)+")=", fdn2c)
print("Error FDN2:", abs(fdn2c-g(c)))

c=a+2*h
print("c=",c)

print("Central:")
fdn2c=fdn2(c, h, valores)
print("FDN2: f'("+str(c)+")=", fdn2c)
print("Error FDN2:", abs(fdn2c-g(c)))

print("Regresiva:")
fdn3c=fdn3(c, h, valores)
print("FDN3: f'("+str(c)+")=", fdn3c)
print("Error FDN3:", abs(fdn3c-g(c)))

c=a+3*h
print("c=",c)

print("Regresiva:")
fdn3c=fdn3(c, h, valores)
print("FDN3: f'("+str(c)+")=", fdn3c)
print("Error FDN3:", abs(fdn3c-g(c)))


"""
In Ternary Search we divide 3 times on every iteration.
It might be better than BInary Search in some cases, but
in worst case scenario it involves more comparisons than
Binary Search and that is why the latter one is preferred.

Time Complexity for Binary search = 2clog2n + O(1)
Time Complexity for Ternary search = 4clog3n + O(1)

The following is recursive formula for counting
comparisons in worst case of Binary Search.

   T(n) = T(n/2) + 2,  T(1) = 1

The following is recursive formula for counting
comparisons in worst case of Ternary Search.

   T(n) = T(n/3) + 4, T(1) = 1
"""
import numpy as np
import time
from rich import print as rprint

def search(array, left, right, query):
	if right >= left:
		mid1 = left + (right - left) // 3
		mid2 = mid1 + (right - left) // 3
		if array[mid1] == query:
			return mid1

		if array[mid2] == query:
			return mid2

		if array[mid1] > query:
			return search(array, left, mid1 - 1, query)

		if array[mid2] < query:
			return search(array, mid2 + 1, right, query)

		return search(array, mid1 + 1, mid2 - 1, query)
	return -1

def main():
	array = np.arange(1, 9999999, 1)
	query = np.random.randint(1, 9999999)
	size = len(array)
	start_time = time.time()
	result = search(array=array, left=0, right=size - 1, query=query)
	stop_time = time.time()
	if result == -1:
		rprint("[red]Element {} is not present in the array.".format(query))
	else:
		rprint("[green]ELement[/green] {} [green]found in the array at index:[/green] {}".format(query, result))
	rprint("[yellow]Total time taken:", stop_time - start_time, "[yellow]seconds.")

if __name__ == "__main__":
	main()


from sympy import *
import sys
sys.path.insert(1, '..')
from quaternion_R_utils import *

px, py, pz = symbols('px py pz')
q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
tie_px, tie_py, tie_pz = symbols('tie_px tie_py tie_pz');
cols, rows = symbols('cols rows');
pi = symbols('pi')
u_kp, v_kp = symbols('u_kp v_kp')

position_symbols = [px, py, pz]
quaternion_symbols = [q0, q1, q2, q3]
tie_point_symbols = [tie_px, tie_py, tie_pz]
all_symbols = position_symbols + quaternion_symbols + tie_point_symbols

RT_wc = matrix44FromQuaternion(px, py, pz, q0, q1, q2, q3)
r=RT_wc[:-1,:-1]
t=Matrix([px, py, pz]).vec()

pos_w=Matrix([tie_px, tie_py, tie_pz]).vec()
bearing = r * pos_w + t;
norm = sqrt(bearing[0]*bearing[0] + bearing[1]*bearing[1] + bearing[2]*bearing[2])
bearing=bearing/norm
latitude=-asin(bearing[1])
longitude=atan2(bearing[0], bearing[2])

u=cols*(0.5 + longitude / (2.0 * pi))
v=rows*(0.5 - latitude/pi)
u_delta = u_kp - u;
v_delta = v_kp - v;

obs_eq = Matrix([u_delta, v_delta]).vec()
obs_eq_jacobian = obs_eq.jacobian(all_symbols)

print(obs_eq)
print(obs_eq_jacobian)

with open("equirectangular_camera_colinearity_quaternion_wc_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void observation_equation_equrectangular_camera_colinearity_quaternion_wc(Eigen::Matrix<double, 2, 1> &delta, double rows, double cols, double pi, double px, double py, double pz, double q0, double q1, double q2, double q3, double tie_px, double tie_py, double tie_pz, double u_kp, double v_kp)\n")
    f_cpp.write("{")
    f_cpp.write("delta.coeffRef(0,0) = %s;\n"%(ccode(obs_eq[0,0])))
    f_cpp.write("delta.coeffRef(1,0) = %s;\n"%(ccode(obs_eq[1,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void observation_equation_equrectangular_camera_colinearity_quaternion_wc_jacobian(Eigen::Matrix<double, 2, 10, Eigen::RowMajor> &j, double rows, double cols, double pi, double px, double py, double pz, double q0, double q1, double q2, double q3, double tie_px, double tie_py, double tie_pz, double u_kp, double v_kp)\n")
    f_cpp.write("{")
    for i in range (2):
        for j in range (10):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(obs_eq_jacobian[i,j])))
    f_cpp.write("}")


from numpy.linalg import matrix_rank
from matplotlib import pyplot as plt
from sklearn.datasets import fetch_olivetti_faces
import numpy as np
from sklearn.decomposition import PCA

np.random.seed(0)
# load the faces (value from 0 to 1)
faces = fetch_olivetti_faces()
img = faces['images']
X = faces['data']
target = faces['target']
y = np.ravel(np.repeat(np.arange(1, 41), 10))

h, w, n = 64, 64, len(img)

val = np.random.choice(n, 16, replace=False)
fig, axs = plt.subplots(4, 4)
fig.suptitle("16 Random Face Images ", fontsize="x-large")
for i in range(16):
    r, c = int(i / 4), i % 4
    axs[r, c].imshow(X[val[i]].reshape(h, w), cmap='gray')
    axs[r, c].axis('off')

fig.savefig("../figures/PcaTrainFaceImages.png")

print('Performing PCA')
mu = np.mean(X, axis=0)
XC = X - mu
pca = PCA()
pca.fit(XC)
V = pca.components_
Z = np.dot(XC, V.T)

fig, axs = plt.subplots(2, 2)
fig.suptitle("PCA on Face Images (Principle components) ", fontsize="x-large")
for i in range(4):
    r, c = int(i / 2), i % 2
    if r == 0 and c == 0:
        # mu plot
        axs[r, c].imshow(mu.reshape(h, w), cmap='gray')
        axs[r, c].axis('off')
        axs[r, c].set_title('Mean')
    else:
        # plots the first three Eigenfaces
        axs[r, c].imshow(V[i - 1].reshape(h, w), cmap='gray')
        axs[r, c].axis('off')
        axs[r, c].set_title('principal Basis {}'.format(i - 1))
fig.savefig("../figures/PrincipalComponentFaceImages.png")

ndx = 125
Ks = [5, 10, 20, matrix_rank(X)]
fig, axs = plt.subplots(2, 2)
fig.suptitle("PCA on Face Images (Reconstructed Images) ", fontsize="x-large")
count = 0
for k in Ks:
    Xrecon = np.dot(Z[np.newaxis, ndx, :k], V[:k, :]) + mu
    r, c = int(count / 2), count % 2
    axs[r, c].imshow(Xrecon.reshape(64, 64), cmap='gray')
    axs[r, c].axis('off')
    axs[r, c].set_title('{} Components'.format(k))
    count += 1
fig.savefig("../figures/PCAReconstructedFaceImages.png")

fig, axs = plt.subplots(1, 1)
fig.suptitle("PCA on Face Images (ReconstructionError) ", fontsize="x-large")
Ks = []
Ks.extend(list(np.arange(0, 10, 1)))
Ks.extend(list(np.arange(10, 50, 5)))
Ks.extend(list(np.arange(50, matrix_rank(X), 25)))
mse = np.zeros(len(Ks))
count = 0
for k in Ks:
    Xrecon = np.dot(Z[:, :k], V[:k, :]) + mu
    err = (Xrecon - X)
    mse[count] = np.sqrt(np.mean(err ** 2))
    count += 1

axs.plot(Ks, mse, '-o')
axs.set_ylabel('MSE')
axs.set_xlabel('K')
fig.savefig("../figures/ReconstructionError.png")

fig, axs = plt.subplots(1, 1)
fig.suptitle("pcaImage Faces (proportion of variance) ", fontsize="x-large")

axs.plot(np.cumsum(pca.explained_variance_)/np.sum(pca.explained_variance_), 'o-')
axs.set_ylabel('proportion of variance')
axs.set_xlabel('K')
fig.savefig("../figures/PCAvariance.png")


import numpy as np

a = np.array([1, 2, 3, 4, 5])
print(a)
print(a.dtype)
print(type(a))
print(np.size(a))

# NumPy will try to coerce all the items to the same type.
a2 = np.array([1, 2.2, 3, 4, 5])
print(a2)
print(a2.dtype)
print(type(a2))
print(np.size(a2))

# shorthand to repeat a sequence 10 times
a3 = np.array([2]*10)
print(a3)

# convert a python range to numpy array
a4 = np.array(range(5))
print(a4)

# To efficiently create an array of a specific size that is initialized with zeros
a5 = np.zeros(5)
print(a5)
print(a5.dtype)

# with types
a6 = np.zeros(3, dtype=int)
print(a6)
print(a6.dtype)

#using numpy arange
a7 = np.arange(5, 10)
print(a7)

a8 = np.arange(0, 10)
a9 = a8 * 2 # multiply a array
print(a9)

a10 = np.array([1, 2, 3])
a11 = np.array([4, 5, 6])
a12 = a10 + a11 #add two arrays
print(a12)

a13 = np.array([[1, 2, 3], [4, 5, 6]]) #two dimensional array
print(a13)
print(a13[0, 2]) #accessing two dimentionsional array
print(a13[0,]) #accessing a entire row
print(a13[:,1])#accessing a entire column

b = np.array([1, 2, 3, 4, 5])
print(b.mean())
print(b.max())
print(b.min())
print(b.std())


# -*- coding: utf-8 -*-
"""
Numerical integration - Simpson's Rule (3 points quadrature equation)
Roughly the error of integration can be estimated as ~ h**4  (the global error)
@author: ssklykov
"""
# %% Import Section
import numpy as np
from sampleFunctions import SampleFuncIntegr as sfint
import inspect
import matplotlib.pyplot as plt

# %% Algorithm implementation
def SimpsonIntegr(a: float, b: float, h: float, y, nDigits: int = 3):
    """
    Simpson's Rule Integration implementation
    Input
    -----
    a, b:
        float [a,b] interval for integration
    h:
        float, step size
    y:
        float, function or method returning single float number and accepting single float number
    """

    if (a >= b) and (int((b-a)/h) <= 1):
        print("Incosistent interval assigning [a,b] or step size h - a and b exchanged, h - decreased")
        holder = b; b = a; a = holder
        h /= 2
    if not((inspect.isfunction(y)) or (inspect.ismethod(y))):
        print("Passed function y(x) isn't the defined method or function")
        return None  # returning null object instead of any result, even equal to zero
    else:
        nPoints = int((b-a)/h) + 1
        evenSum = 0.0; oddSum = 0.0; intSum = 0.0
        for i in range(2, nPoints-2, 2):
            x = a + i*h; evenSum += y(x)
        for i in range(1, nPoints-1, 2):
            x = a + i*h; oddSum += y(x)
        intSum = (h/3)*(y(a) + y(b) + 2*evenSum + 4*oddSum)
        return round(intSum, nDigits)

# %% Adaptive calling of the Simpson's rule(above)
def AdaptiveSimpsonInt(a: float, b: float, h: float, y, nDigits: int = 3, epsilon: float = 0.01, nMaxIterations: int = 3):
    """
    Adaptive calling of Simpson's Rule for numerical integration. Epsilon - difference of two sub
    sequent calculated integrals (condition for stopping) - absolute error; nMaxIterations - maximum number of iterations
    of lowering step size h (no more than 30)
    """
    intSum1 = SimpsonIntegr(a, b, h, y, nDigits*4); h = h/2
    intSum2 = SimpsonIntegr(a, b, h, y, nDigits*4)  # nDigits should be more  than digits in epsilon!
    # As described, max int number: 2**32 -1, so it's impossible to make more iterations using nPoints evaluation
    if (nMaxIterations > 30):
        print("impossible to make so many halving iterations")
        nMaxIterations = 30
    j = 1  # number of iterations
    while((j < nMaxIterations) and (abs(intSum2-intSum1) > epsilon*intSum2)):  # |I2-I1| <= epsilon*I2 - relative error check
        intSum1 = intSum2
        h = h/2
        intSum2 = SimpsonIntegr(a, b, h, y, nDigits*4)  # nDigits should be more  than digits in epsilon!
        j += 1
    print(j, "number of iterations")
    return round(intSum2, nDigits)

# %% Parameters for testing
nDigits = 2; a = 0; b = 2; nSample = 1; h = 0.05
fClass = sfint(nDigits, nSample)  # making sample of the class contained the sample function
# making x and y values for plotting
nDigits2 = 3; a2 = 0; b2 = 2; h2 = 0.3; epsilon = 1e-4; nMaxIterations = 10
fClass2 = sfint(nDigits2, nSample)  # using the sample function with a unknown analytical integral form
nPoints2 = int(50*(b2-a2)/h2) + 1
x = np.zeros(nPoints2); y = np.zeros(nPoints2)
for i in range(nPoints2):
    x[i] = a + i*(h2/50); y[i] = fClass2.sampleF(x[i])
# plot sample function from interval [a,b]
fig = plt.figure(); plt.plot(x,y); plt.grid()

# %% Testing
integral = SimpsonIntegr(a, b, h, fClass.sampleF, nDigits)
print(integral, " - calculated integral value for 1st sample f(x)")
print("1 - exact value from Newton-Leibniz equation F(b) - F(a)")  # F(a) = 0; F(b) = 1; F(x) = x^3 - x^2 - 1.5*x
integral2 = SimpsonIntegr(a2, b2, h2, fClass2.sampleF, nDigits2)
print(integral2, " - integral value for 1st sample f(x) w/t adaptation")
integral22 = AdaptiveSimpsonInt(a2, b2, h2, fClass2.sampleF, nDigits2, epsilon, nMaxIterations)
print(integral22, " - integral value for 2nd sample f(x) with adaptation")


import numpy as np

def hhi_index(firm_sizes: np.ndarray, weights: np.ndarray = None) -> float:
    r"""Herfindahl–Hirschman Index

    A common measure of market concentration, defined as

    $$
    H = \sum_{i=1}^{N} s_i^2
    $$

    where $s_i$ is firm $i$'s market share in the industry of $N$ firms.

    Args:
        firm_sizes (np.ndarray): (n_firms,) array of firm sizes suchs as sales, used to compute market shares
        weights (np.ndarray): (n_firms,) array of the weights given to each firm's market share. Defaults to equal weights.

    !!! note
        If `weights` are provided, the HHI index is computed as:

        $$
        H = \sum_{i=1}^{N} s_i^2 \times w_i
        $$

        where $w_i$ is the weight given to the market share of firm $i$.

    Returns:
        float: HHI-index for the industry

    Examples:
        >>> import numpy as np
        >>> from frds.measures import hhi_index

        7 firms with equal sales
        >>> firm_sales = np.array([1,1,1,1,1,1,1])
        >>> hhi_index(firm_sales)
        0.14285714285714285

        6 firms, of which 1 has much larger sales
        >>> firm_sales = np.array([100,1,1,1,1,1])
        >>> hhi_index(firm_sales)
        0.9074829931972791

    References:
        - [Wikipedia](https://en.wikipedia.org/wiki/Herfindahl%E2%80%93Hirschman_Index)

    Todo:
        - [ ] Allow `firm_sizes` to be multidimensional.
        - [ ] Check validity of input data (no negative firm sizes, etc.).
        - [x] Allow market shares to be weighted.
    """
    if weights is None:
        weights = np.ones(firm_sizes.shape)
    mkt_shares = firm_sizes / np.sum(firm_sizes)
    return np.sum(np.square(mkt_shares) * weights)


# -*- coding: utf-8 -*-
"""
@author: vladimirnesterov
Examples for ten little algorithms
(Ten Little Algorithms by Jason Sachs from here https://www.embeddedrelated.com/showarticle/760.php)
"""

import ten_little_algorithms as tla

print("")
print("  --- ----------------------- *** ----------------------- ---  ")
print("   Euclidean Algorithm to find greatest common divider (GCD)   ")
print("")
from math import gcd
a=1071
b=462
print("Python gcd function gcd(", a,",", b, ") =",gcd(a,b))
print("Euclidean algorithm gcd(", a,",", b, ") =",tla.euclidean_gcd(a,b))
print("")

print("  --- ----------------------- *** ----------------------- ---   ")
print(" Extended Euclidean Algorithm to find GCD and Bézout's identity ")
print("")
a=462
b=1071
gdc,x,y = tla.euclidean_ext_gcd(a,b)
print("Euclidean algorithm gcd(", a,",", b, ") =",gdc)
print("Coefficients of Bézout's identity:",x, "and", y)
print("such that ",a, "*", x, "+", b, "*", y, "=", a*x+b*y)
print("")

print("  --- ----------------------- *** ----------------------- ---  ")
print("              Newton's method for finding roots                ")
print("")
from scipy import optimize

def f(x: float) -> float:
    return x**2 - 20 * x + 5

def f_derivative(x: float) -> float:
    return 2 * x - 20

x0 = 100 #1
eps = 1e-7
kmax =1e3

newton_result = tla.newton(f, f_derivative, x0, eps, kmax)
scipy_result = optimize.root(f, x0)

print("Test function is (x^2 - 20*x + 5), first guess is", x0)
print("Newton's method root =",newton_result, ", f(newton root)=", f(newton_result))
print("Scipy function roots =",scipy_result.x[0], ", f(scipy root)= ", f(scipy_result.x[0]))
print("")

print("  --- ----------------------- *** ----------------------- ---  ")
print("                  Russian Peasant algorithm                    ")
print("")
a=462
b=1071
mul_result = tla.rpmul(a,b)
print("Russian Peasant Multiplication ", a,"*", b, "=",mul_result)
print("Correct is:", a*b)
a=2
b=23
exp_result = tla.rpexp(a,b)
print("Russian Peasant Exponentiation ", a,"**", b, "=",exp_result)
print("Correct is:", a**b)
print("")

print("  --- ----------------------- *** ----------------------- ---  ")
print("                The Single-Pole Low-Pass Filter                ")
print("")

import matplotlib.pyplot as plt
from matplotlib import ticker
import numpy as np

cutoff_freq = 2000
smpl_freq = 8000

alpha, h, w = tla.sp_iir_lpf(cutoff_freq, smpl_freq)

# Plot example is from scipy.signal.freqz function description
fig, ax1 = plt.subplots()
ax1.set_title('SP IIR filter frequency responses with cutoff frequency = '+str(cutoff_freq/(2*smpl_freq))+'$\pi$')

ax1.plot(w, 20 * np.log10(abs(h)), 'b')
ax1.set_ylabel('Amplitude [dB]', color='b')
ax1.set_xlabel('Frequency [rad/sample]')

ax2 = ax1.twinx()
angles = np.unwrap(np.angle(h))
ax2.plot(w, angles, 'g')
ax2.set_ylabel('Angle (radians)', color='g')
ax2.grid()
ax2.axis('tight')
ax2.xaxis.set_major_formatter(ticker.FormatStrFormatter('%g $\pi$'))
ax2.xaxis.set_major_locator(ticker.MultipleLocator(base=0.25))
plt.show()

print("The single-pole IIR low-pass filter with cutoff frequency", cutoff_freq,"\nhas coefficient alpha =",alpha)
print("See the frequency response on the figure.")
print("")

print("  --- ----------------------- *** ----------------------- ---  ")
print("                Statistic and Welford's method                 ")
print("")
import test_signals as ts
w_mean, w_var = tla.welford(ts.noise_signal)
np_mean = np.mean(ts.noise_signal)
np_var = np.var(ts.noise_signal, ddof=1)
print("Welford:", w_mean, w_var)
print("numpy:  ", np_mean, np_var)
print("")


""" this shows the logsumexp trick"""

import math
import numpy as np

def logsumexp1(vector):
  s = 0
  for i in vector:
    s += math.exp(i)
  return math.log(s)

def logsumexp2(vector):
  s = 0
  A = -1 * max(vector)
  for i in vector:
     s += math.exp(i + A)
  return math.log(s) - A

print(logsumexp1([1,2,3,4,5]))

print(logsumexp2([1,2,3,4,5]))

def logsumexp(vector):
  A = -np.max(vector)
  s =  np.sum(np.exp(np.add(vector, A)))
  return np.log(s) - A

print logsumexp([1,2,3,4,5])


import numpy as np

def isMaxMagUnity(real_part, imag_part):

    '''
    Description: Checks if the maximum magnitude of the arguments is one

    Note: Consider this problem on the complex plane ... no vector magnitude larger than one
    Note: accuracy to account for computational error is included in the check

    Parameters:
      real_part : (numpy array) the array of real parts of the complex object
      imag_part : (numpy array) the array of imag parts of the complex object

    returns: returns nothing if passed, if not returns -1
    '''

    magnitude = np.sqrt(np.square(real_part) + np.square(imag_part))

    max_magnitude = np.max(magnitude)

    accuracy = 0.05
    threshold_upper = 1 + accuracy
    threshold_lower = 1 - accuracy

    if (max_magnitude >= threshold_upper or max_magnitude <= threshold_lower):

        print("The maximum magnitude of the args is not unity!")
        print("Maximum magnitude = " + str(max_magnitude))
        return -1


# Linear Regression: Inverse Matrix Method
#----------------------------------
#
# This function shows how to use Tensorflow to
# solve linear regression via the matrix inverse.
#
# Given Ax=b, solving for x:
#  x = (t(A) * A)^(-1) * t(A) * b
#  where t(A) is the transpose of A

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow.python.framework import ops
ops.reset_default_graph()

# Create graph
sess = tf.Session()

# Create the data
x_vals = np.linspace(0, 10, 100)
y_vals = x_vals + np.random.normal(0, 1, 100)

# Create design matrix
x_vals_column = np.transpose(np.matrix(x_vals))
ones_column = np.transpose(np.matrix(np.repeat(1, 100)))
A = np.column_stack((x_vals_column, ones_column))

# Create b matrix
b = np.transpose(np.matrix(y_vals))

# Create tensors
A_tensor = tf.constant(A)
b_tensor = tf.constant(b)

# Matrix inverse solution
tA_A = tf.matmul(tf.transpose(A_tensor), A_tensor)
tA_A_inv = tf.matrix_inverse(tA_A)
product = tf.matmul(tA_A_inv, tf.transpose(A_tensor))
solution = tf.matmul(product, b_tensor)

solution_eval = sess.run(solution)

# Extract coefficients
slope = solution_eval[0][0]
y_intercept = solution_eval[1][0]

print('slope: ' + str(slope))
print('y_intercept: ' + str(y_intercept))

# Get best fit line
best_fit = []
for i in x_vals:
  best_fit.append(slope*i+y_intercept)

# Plot the results
plt.plot(x_vals, y_vals, 'o', label='Data')
plt.plot(x_vals, best_fit, 'r-', label='Best fit line', linewidth=3)
plt.legend(loc='upper left')
plt.show()


import numpy as np
import scipy.sparse as spar
import scipy.linalg as la
from scipy.sparse import linalg as sla

def adj_mat(datafile, n):
    """ Parse the data stored in 'datafile' and form the
    adjacency matrix of the corresponding graph.
    'n' is the number of rows of the nxn sparse matrix formed. """
    adj = spar.dok_matrix((n,n))
    with open(datafile, 'r') as f:
        for L in f:
            L = L.strip().split()
            try:
                x, y = int(L[0]), int(L[1])
                adj[x, y] = 1
            except:
                continue
    return adj

def page_rank_dense_lstsq(datafile, d, datasize, n=None):
    """ Solve the page rank problem, given the data in 'datafile',
    the dampening factor 'd', the size 'datasize' of the dataset,
    and the number 'n' of nodes to include.
    Have 'n' default to None.
    Use the method involving least squares."""
    data = adj_mat(datafile, n)
    A = np.asarray(data.tocsr()[:n, :n].todense())
    #data is dense and is of type matrix
    e = np.ones(n)
    for i, v in enumerate(A.sum(1)):
        if v == 0:
            A[i] = e

    K = ((1./A.sum(1))[:,np.newaxis]*A).T
    K *= - d
    np.fill_diagonal(K, K.diagonal() + 1)
    R = la.lstsq(K, (1-d)*e/float(n))
    max_rank = R[0].max()

    return max_rank, np.where(R[0]==max_rank)[0]

def page_rank_dense_iter(datafile, d, datasize, n=None, tol=1E-5):
    """ Solve the page rank problem, given the data in 'datafile',
    the dampening factor 'd', the size 'datasize' of the dataset,
    the number 'n' of nodes to include, and a tolerance 'tol'
    to use to determine when to stop iterating.
    Have 'n' default to None.
    Use the iterative method described in the lab. """
    pass

def page_rank_dense_eig(datafile, d, datasize, n=None):
    """ Solve the page rank problem, given the data in 'datafile',
    the dampening factor 'd', the size 'datasize' of the dataset,
    and the number 'n' of nodes to include.
    Have 'n' default to None.
    Use the eigenvalue method described in the lab. """
    pass

def sparse_pr(datafile, d, datasize, n=None, tol=1e-5):
    """ Solve the page rank problem, given the data in 'datafile',
    the dampening factor 'd', the size 'datasize' of the dataset,
    the number 'n' of nodes to include, and a tolerance 'tol'
    to use to determine when to stop iterating.
    Have 'n' default to None.
    Use the iterative method described in the lab.
    Use only sparse matrix operations. """

    A = data.tocsc()[:n, :n]
    s = A.sum(1)
    diag = 1./s
    sinks = s==0
    diag[sinks] = 0
    K = spar.spdiags(diag.squeeze(1), 0, n, n).dot(A).T

    d = .85
    convDist = 1
    Rinit = np.ones((n, 1))/float(n)
    Rold = Rinit
    while convDist > tol:
        Rnew = d*K.dot(Rold) + (1-d)*Rinit + (d*Rold[sinks].sum())*Rinit
        convDist = la.norm(Rnew-Rold)
        Rold = Rnew

    max_rank = Rnew.max()
    return max_rank, Rnew[Rnew==max_rank]


# -*- coding: utf-8 -*-
"""
Created on Fri Dec 21 14:36:49 2018

@author: Zymieth
"""
import numpy as np

def merge_sort(num_list):
    '''
    Implementation of vanilla merge sort, O(nlogn)
    '''
    if len(num_list) == 1:
        return num_list

    l = len(num_list)//2
    l1 = merge_sort(num_list[:l])
    l2 = merge_sort(num_list[l:])

    v = []
    i = 0
    j = 0

    # merging subroutine, break from loop when one of the pointers falls off
    for k in range(len(l1)+len(l2)):
        if j == len(l2) or i == len(l1): break
        if l1[i] <= l2[j]:
            v.append(l1[i])
            i += 1
        else:
           v.append(l2[j])
           j += 1
    # handling final remaining elements in either l1 or l2
    while(i < len(l1)):
        v.append(l1[i])
        i += 1
    while(j < len(l2)):
        v.append(l2[j])
        j += 1
    return v

def count_inversions(num_list, count = 0):
    '''
    Counts number of inversions in array of numbers num_list
    '''
    if len(num_list) == 1:
        return num_list, count

    l = len(num_list)//2
    l1, count = count_inversions(num_list[:l], count)
    l2, count = count_inversions(num_list[l:], count)

    v = []
    i = 0
    j = 0

    # merging subroutine, break from loop when one of the pointers falls off
    for k in range(len(l1)+len(l2)):
        if j == len(l2) or i == len(l1): break
        if l1[i] <= l2[j]:
            v.append(l1[i])
            i += 1
        else:
           v.append(l2[j])
           j += 1
           count += len(l1)-i
    # handling final remaining elements in either l1 or l2
    while(i < len(l1)):
        v.append(l1[i])
        i += 1
    while(j < len(l2)):
        v.append(l2[j])
        j += 1
    return v, count

###################################################### TESTS: merge_sort
n = np.array([2,5,1,3,4])
n = merge_sort(n)
assert np.all([n[i] <= n[i+1] for i in range(len(n)-1)])

n = np.array([6,3,6,8,3,2,-40,20,4.2,4,0,3,215])
n = merge_sort(n)
assert np.all([n[i] <= n[i+1] for i in range(len(n)-1)])

n = np.random.rand(100)
n = merge_sort(n)
assert np.all([n[i] <= n[i+1] for i in range(len(n)-1)])

n = np.loadtxt("L:\Algorithms\tests-merge.txt")
n = merge_sort(np.array(n))
assert np.all([n[i] <= n[i+1] for i in range(len(n)-1)])

###################################################### TESTS: count_inversions
n = np.array([2,5,1,3,4])
n, c = count_inversions(n)
assert np.all([n[i] <= n[i+1] for i in range(len(n)-1)])
assert c == 4

n = np.loadtxt("L:\Algorithms\tests-merge.txt")
n, c = count_inversions(n)
assert np.all([n[i] <= n[i+1] for i in range(len(n)-1)])
assert c == 2407905288


import numpy as np

def divide(matrix2D):

    r, c = matrix2D.shape
    r2, c2 = r//2, c//2
    return matrix2D[:r2, :c2], matrix2D[:r2, c2:], matrix2D[r2:, :c2], matrix2D[r2:, c2:]

def strassen(x, y): #Code to multiply any 2^n Dimensional matrix

    if len(x) == 1:
        return x * y #Base case

    a, b, c, d = divide(x)
    e, f, g, h = divide(y)

    p1 = strassen(a, f - h)
    p2 = strassen(a + b, h)
    p3 = strassen(c + d, e)
    p4 = strassen(d, g - e)
    p5 = strassen(a + d, e + h)
    p6 = strassen(b - d, g + h)
    p7 = strassen(a - c, e + f)

    c11 = p5 + p4 - p2 + p6
    c12 = p1 + p2
    c21 = p3 + p4
    c22 = p1 + p5 - p3 - p7

    c = np.vstack((np.hstack((c11, c12)), np.hstack((c21, c22))))

    return c

#sample
A = np.random.rand(4,4)
B = np.random.rand(4,4)

print("A =",A)
print("B =", B)
print("A*B =",strassen(A,B))


import numpy as np
# **The following class contains methods to calculate distance between two points using various techniques**

# **Formula to calculate Eucledian distance:**
#
# <math>\begin{align}D(x, y) = \sqrt{ \sum_i (x_i - y_i) ^ 2 }\end{align}</math>

# **Formula to calculate Manhattan Distance:**
#
# <math>\begin{align}D(x, y) = \sum_i |x_i - y_i|\end{align}</math>

# **Formula to calculate Hamming Distance:**
#
# <math>\begin{align}D(x, y) = \frac{1}{N} \sum_i \delta_{x_i, y_i}\end{align}</math>

class distanceMetrics:
    '''
    Description:
        This class contains methods to calculate various distance metrics
    '''
    def __init__(self):
        '''
        Description:
            Initialization/Constructor function
        '''
        pass

    def euclideanDistance(self, vector1, vector2):
        '''
        Description:
            Function to calculate Euclidean Distance

        Inputs:
            vector1, vector2: input vectors for which the distance is to be calculated
        Output:
            Calculated euclidean distance of two vectors
        '''
        self.vectorA, self.vectorB = vector1, vector2
        if len(self.vectorA) != len(self.vectorB):
            raise ValueError("Undefined for sequences of unequal length.")
        distance = 0.0
        for i in range(len(self.vectorA)-1):
            distance += (self.vectorA[i] - self.vectorB[i])**2
        return (distance)**0.5

    def manhattanDistance(self, vector1, vector2):
        """
        Desription:
            Takes 2 vectors a, b and returns the manhattan distance
        Inputs:
            vector1, vector2: two vectors for which the distance is to be calculated
        Output:
            Manhattan Distance of two input vectors
        """
        self.vectorA, self.vectorB = vector1, vector2
        if len(self.vectorA) != len(self.vectorB):
            raise ValueError("Undefined for sequences of unequal length.")
        return np.abs(np.array(self.vectorA) - np.array(self.vectorB)).sum()

    def hammingDistance(self, vector1, vector2):
        """
        Desription:
            Takes 2 vectors a, b and returns the hamming distance
            Hamming distance is meant for discrete-valued vectors, though it is a
            valid metric for real-valued vectors.
        Inputs:
            vector1, vector2: two vectors for which the distance is to be calculated
        Output:
           Hamming Distance of two input vectors
        """
        self.vectorA, self.vectorB = vector1, vector2
        if len(self.vectorA) != len(self.vectorB):
            raise ValueError("Undefined for sequences of unequal length.")
        return sum(el1 != el2 for el1, el2 in zip(self.vectorA, self.vectorB))


#!/usr/bin/env python3

import csv
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from math import sqrt
import argparse

def		train(mileage, price, learning_rate, n_epochs):
	m = float(len(mileage))
	theta0, theta1 = 0, 0
	for _ in range(n_epochs):
		price_estimate = mileage * theta1 + theta0
		diff = price_estimate - price
		tmp_theta0 = np.sum(diff)*learning_rate/m
		tmp_theta1 = np.sum(diff*mileage)*learning_rate/m
		theta0 -= tmp_theta0
		theta1 -= tmp_theta1
	return theta0, theta1

def		main():
	# Parse arguments
	my_parser = argparse.ArgumentParser(description='Train a linear regression model on a univariate dataset.')
	my_parser.add_argument('Path', metavar='path', type=str, help='The path to the csv file containing the data.')
	my_parser.add_argument('-n', '-Number of epochs', type=int, default=1000)
	my_parser.add_argument('-l', '-Learning rate', type=float, default=0.1)
	args = my_parser.parse_args()
	if (args.n < 1):
		print("The number of epochs must be larger than zero.")
		exit(1)
	if (args.l <= 0):
		print("The learning rate must be larger than zero.")
		exit(1)

	# Read csv file
	try:
		with open(args.Path) as data_file:
			csv_reader = csv.reader(data_file, delimiter=',')
			mileage, price = [], []
			count = 0
			for row in csv_reader:
				if (count):
					mileage.append(int(row[0]))
					price.append(int(row[1]))
				count += 1
	except Exception as e:
		print(f"Can't open {args.Path} or it's not a valid csv file.")
		print(e)
		exit(1)
	mileage = np.array(mileage)
	price = np.array(price)

	# Data normalization
	mileage_scaler = MinMaxScaler()
	mileage = mileage.reshape(-1, 1)
	mileage_norm = mileage_scaler.fit_transform(mileage)

	price_scaler = MinMaxScaler()
	price = price.reshape(-1, 1)
	price_norm = price_scaler.fit_transform(price)

	# Training
	theta0_norm, theta1_norm = train(mileage_norm, price_norm, args.l, args.n)
	predicted_price_norm = theta1_norm * mileage_norm + theta0_norm

	# Rescaling
	predicted_price = price_scaler.inverse_transform(predicted_price_norm)

	# Calculating root mean squared error
	rmse = sqrt(np.sum((predicted_price - price) * (predicted_price - price))/(count - 1))
	print(f"RMSE: {rmse}")

	# Plotting
	plt.scatter(mileage, price)
	plt.plot(mileage, predicted_price, color='red')
	plt.xlabel("Mileage (in Km)")
	plt.ylabel("Price (in Euros)")
	plt.show()

	# Calculating real values of theta0 and theta1
	theta0 = (theta0_norm - (theta1_norm * mileage.min()) / (mileage.max() - mileage.min())) * (price.max() - price.min()) + price.min()
	theta1 = (theta1_norm / (mileage.max() - mileage.min())) * (price.max() - price.min())

	# Saving theta0 and theta1 to a file
	f = open("parameters.txt", 'w')
	f.write(f"{theta0}\n{theta1}\n")

if __name__ == "__main__":
    main()


# A Gabriel's Horn, brought to you by PharaohCola13

import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.pyplot as plt
from matplotlib import *
from numpy import *
from mpl_toolkits.mplot3d.art3d import *
from matplotlib.animation import *

name = "Gabriel's-Horn"

def shape(fig, alpha, color, edge_c, edge_w, grid, sides, edges, multi_pi, radius, height):
	# Definition of x
	def x_(u, v):
		x = u
		return x

	# Definition of y
	def y_(u, v):
		y = (a * cos(v)) / u
		return y

	# Definition of z
	def z_(u, v):
		z = (a * sin(v)) /u
		return z

	a = radius # changes radius of the entire thing

	h = height

	# Value of the angles
	s = sides
	u = linspace(1, h, s + 1)
	v = linspace(0, 2 * pi, edges)

	u, v = meshgrid(u, v)

	# Symbolic representation
	x = x_(u, v)
	y = y_(u, v)
	z = z_(u, v)

	# Figure Properties
	ax = p3.Axes3D(fig)
	ax.set_facecolor('black')  # Figure background turns black

	# Axis Properties
	plt.axis(grid)  # Turns off the axis grid
	plt.axis('equal')

	# Axis Limits
	ax.set_xlim(-4, 4)
	ax.set_ylim(-4, 4)
	ax.set_zlim(-4, 4)

	# Surface Plot
	horn = ax.plot_surface(x, y, z)

	horn.set_alpha(alpha)  # Transparency of figure
	horn.set_edgecolor(edge_c)  # Edge color of the lines on the figure
	horn.set_linewidth(edge_w)  # Line width of the edges
	horn.set_facecolor(color)  # General color of the figure


from numpy import *

def rmserr( set1, set2 ):
    """Compute and return RMS error for two sets of equal length involving the same set of samples."""
    tot = 0.
    for i in range(len(set1)):
        tot+= (set1[i] - set2[i])**2
    return sqrt(tot/float(len(set1)))

def correl(x,y ):
    """For two data sets x and y of equal length, calculate and return r, the product moment correlation. """
    if len(x)!=len(y):
        print("ERROR: Data sets have unequal length.\n")
        raise LengthError

    #Compute averages (try not to require numerical library)
    avgx=sum(x)/float(len(x))
    avgy=sum(y)/float(len(y))

    #Compute standard deviations
    sigmax_sq=0
    for elem in x:
        sigmax_sq+=(elem-avgx)**2
    sigmax_sq=sigmax_sq/float(len(x))
    sigmay_sq=0
    for elem in y:
        sigmay_sq+=(elem-avgy)**2
    sigmay_sq=sigmay_sq/float(len(y))

    sigmax=sqrt(sigmax_sq)
    sigmay=sqrt(sigmay_sq)

    #Compute numerator of r
    num=0
    for i in range(len(x)):
        num+=(x[i]-avgx)*(y[i]-avgy)
    #Compute denominator of r
    denom=len(x)*sigmax*sigmay

    corr = num/denom
    return corr

def percent_within_half( x, y):
    """Takes two sets, x and y, of equal length, and returns the percentage of values within 0.5 units."""

    diff = x - y
    indices = where( abs(diff) < 0.5 )
    return 100.*float(len(indices[0]) )/float(len(x))


import numpy as np
import math

def makeTranslation(x, y, z):
    """
    return the 4x4 numpy array transpose of the
    3x4
    """
    m = np.eye(4)
    m[:3,3] = x,y,z
    return m

def makeRotationX(theta):
    c = math.cos(theta)
    s = math.sin(theta)
    m = np.eye(4)
    m[1,1] = c
    m[1,2] = -s
    m[2,1] = s
    m[2,2] = c
    return m

def makeRotationY(theta):
    c = math.cos(theta)
    s = math.sin(theta)
    m = np.eye(4)
    m[0,0] = c
    m[0,2] = s
    m[2,0] = -s
    m[2,2] = c
    return m

def makeRotationZ(theta):
    c = math.cos(theta)
    s = math.sin(theta)
    m = np.eye(4)
    m[0,0] = c
    m[0,1] = -s
    m[1,0] = s
    m[1,1] = c
    return m

def makeQuaternionMatrix(x, y, z, w):
    n = math.sqrt(x**2 + y**2 + z**2 + w**2)
    qx = x/n
    qy = y/n
    qz = z/n
    qw = w/n
    m = np.eye(3)
    m[0, 0] =  1 - 2*qy**2 - 2*qz**2
    m[0, 1] = 2*qx*qy - 2*qz*qw
    m[0, 2] = 2*qx*qz + 2*qy*qw
    m[1, 0] = 2*qx*qy + 2*qz*qw
    m[1, 1] = 1 - 2*qx**2 - 2*qz**2
    m[1, 2] = 2*qy*qz - 2*qx*qw
    m[2, 0] = 2*qx*qz - 2*qy*qw
    m[2, 1] = 2*qy*qz + 2*qx*qw
    m[2, 2] = 1 - 2*qx**2 - 2*qy**2
    return m
# end def

def applyQuaternion(coords, m3):
    """ assume we are applying rows of vectors
    [
    [x1, y1, z1],
        ...             * m3.T
    [xn, yn, zn]
    ]
    """
    return np.dot(coords, m3.T)
# end def

def applyGeomStack(coords, stack):
    pass
# end def

def applyTransform(coords, m4):
    """ assume we are applying rows of vectors
    [
    [x1, y1, z1, 1.],
        ...             * m4[:3, :].T
    [xn, yn, zn, 1.]
    ]

    where m4 is the total 4x4 transfomation matrix
    ['n11','n12', 'n13', 'n14',
     'n21', 'n22', 'n23', 'n24',
     'n31', 'n32', 'n33', 'n34',
     'n41', 'n42', 'n43', 'n44'])
    """

    rows, cols = coords.shape
    # print(rows, cols, coords.dtype)
    stacked_coords = np.hstack((coords, np.ones((rows, 1))))
    m4x3 = m4[:3,:].T   # cut off last row so we end up with a (rows , 3) product
    return np.dot(stacked_coords, m4x3)


import math
import numpy as np
from skimage.metrics import structural_similarity

def psnr(label, outputs, max_val = 1.0):
    """
    Computes Peak Signal to Noise Ratio
    PSNR = 20 * log_10(max_val / sqrt(MSE))

    Definitions
        PSNR
            Peak Signal to Noise Ratio (the higher the better)
            https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio#Definition

        MSE
            Mean Square Error

    Inputs
        :label: <torch.tensor> the ground truth image
        :outputs: <torch.tensor> the resulting super resoluted image
        :max_val: <float> the highest pixel value

    Outputs
        :returns: psnr(label, outputs)
    """
    label = label.cpu().detach().numpy()
    outputs = outputs.cpu().detach().numpy()
    img_diff = outputs - label
    rmse = math.sqrt(np.mean((img_diff) ** 2))
    if rmse == 0:
        return 100
    else:
        PSNR = 20 * math.log10(max_val / rmse)
        return PSNR

def ssim(label, outputs):
    """
    Calculates the Structural Similairty Index Measure
    SSIM = L(x, y)^a C(x, y)^b S(x, y)^c

    Definitions
        SSIM
            Structural Similairty Index Measure (the higher the better)
            https://en.wikipedia.org/wiki/Structural_similarity

    Inputs
        :label: <torch.tensor> the ground truth image
        :outputs: <torch.tensor> the resulting super resoluted image

    Outputs
        :returns: ssim(label, outputs)
    """
    score, loss = structural_similarity(label, outputs, full=True, multichannel=True)
    return score


'''
data_normalization.py
https://aimldl.blog.me/221627895429

https://livebook.manning.com/book/deep-learning-with-python/chapter-3/190
'''
from keras.datasets import boston_housing
import numpy as np

# Load data
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
print(f'train_data.shape = {train_data.shape}' )  # (404, 13)
print(f'test_data.shape = {test_data.shape}' )    # (102, 13)
print( train_data[0] )
#[  1.23247   0.        8.14      0.        0.538     6.142    91.7
#   3.9769    4.      307.       21.      396.9      18.72   ]
print( train_targets[0] )
# 15.2

# Prepare data
#   Normalize the data with respect to the features
mean = train_data.mean( axis=0 )
std  = train_data.std( axis=0 )

# Option 1
x_train = (train_data - mean) / std
x_test  = (test_data - mean) / std

# Option 2
train_data -= mean
train_data /= std
test_data  -= mean
test_data  /= std

print( 'Compare the normalized values.' )
print( x_train[0] )
print( train_data[0] )
#Compare the normalized values.
#[-0.27224633 -0.48361547 -0.43576161 -0.25683275 -0.1652266  -0.1764426
#  0.81306188  0.1166983  -0.62624905 -0.59517003  1.14850044  0.44807713
#  0.8252202 ]
#[-0.27224633 -0.48361547 -0.43576161 -0.25683275 -0.1652266  -0.1764426
#  0.81306188  0.1166983  -0.62624905 -0.59517003  1.14850044  0.44807713
#  0.8252202 ]

print( x_test[0] )
print( test_data[0] )
#[ 1.55369355 -0.48361547  1.0283258  -0.25683275  1.03838067  0.23545815
#  1.11048828 -0.93976936  1.67588577  1.5652875   0.78447637 -3.48459553
#  2.25092074]
#[ 1.55369355 -0.48361547  1.0283258  -0.25683275  1.03838067  0.23545815
#  1.11048828 -0.93976936  1.67588577  1.5652875   0.78447637 -3.48459553
#  2.25092074]


import sys
sys.path.append('.')
from sslplay.data.digits import DataDigits

import matplotlib.pyplot as plt

from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import numpy as np

obj_data = DataDigits()
obj_data.load()

X = obj_data.X
y = obj_data.y
target_names = np.array(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"])
colors = np.array(['black', 'lime', 'darkorange',
'darkred', 'chocolate', 'yellow', 'olive',
'cyan', 'darkgrey', 'darkgreen'])

scaler = StandardScaler()
pca = PCA(n_components=6)
np.random.seed(1102)

X_r = pca.fit(scaler.fit_transform(X)).transform(scaler.fit_transform(X))
array_subset = np.random.choice(range(X_r.shape[0]), size=500, replace=False)
X_r = X_r[array_subset, :]
y_r = y[array_subset]

array_classes_show = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
plt.figure()
for color, i, target_name in zip(colors[array_classes_show], \
array_classes_show, target_names[array_classes_show]):
    plt.scatter(
        X_r[y_r == i, 0], X_r[y_r == i, 1],
        color=color, alpha=.8, lw=2,
        label=target_name
    )
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.suptitle('PCA - DIGITS')
plt.title("Explained variance ratio: %s"
      % str(pca.explained_variance_ratio_[[0, 1]]))
plt.xlabel("Dimension 1")
plt.ylabel("Dimension 2")
plt.legend(fancybox=True, framealpha=1)
plt.show()

"""
array_classes_show = [1,2,3,6,8,9]
plt.figure()
for color, i, target_name in zip(colors[array_classes_show], \
array_classes_show, target_names[array_classes_show]):
    plt.scatter(
        X_r[y_r == i, 2], X_r[y_r == i, 3],
        color=color, alpha=.8, lw=2,
        label=target_name
    )
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.suptitle('PCA - DIGITS')
plt.title("Explained variance ratio: %s"
      % str(pca.explained_variance_ratio_[[2, 3]]))
plt.xlabel("Dimension 3")
plt.ylabel("Dimension 4")
plt.legend(fancybox=True, framealpha=1)
plt.show()

array_classes_show = [2,6,8,9]
plt.figure()
for color, i, target_name in zip(colors[array_classes_show], \
array_classes_show, target_names[array_classes_show]):
    plt.scatter(
        X_r[y_r == i, 4], X_r[y_r == i, 5],
        color=color, alpha=.8, lw=2,
        label=target_name
    )
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.suptitle('PCA - DIGITS')
plt.title("Explained variance ratio: %s"
      % str(pca.explained_variance_ratio_[[4, 5]]))
plt.xlabel("Dimension 5")
plt.ylabel("Dimension 6")
plt.legend(fancybox=True, framealpha=1)
plt.show()
"""


from scipy.stats import lognorm, norm
import numpy as np

def make_normal_from_interval(lb, ub, alpha):
    ''' Creates a normal distribution SciPy object from intervals.

    This function is a helper to create SciPy distributions by specifying the
    amount of wanted density between a lower and upper bound. For example,
    calling with (lb, ub, alpha) = (2, 3, 0.95) will create a Normal
    distribution with 95% density between 2 a 3.

    Args:
        lb (float): Lower bound
        ub (float): Upper bound
        alpha (float): Total density between lb and ub

    Returns:
        scipy.stats.norm

    Examples:
        >>> dist = make_normal_from_interval(-1, 1, 0.63)
        >>> dist.mean()
        0.0
        >>> dist.std()
        1.1154821104064199
        >>> dist.interval(0.63)
        (-1.0000000000000002, 1.0)

    '''
    z = norm().interval(alpha)[1]
    mean_norm = (ub + lb) / 2
    std_norm = (ub - lb) / (2 * z)
    return norm(loc=mean_norm, scale=std_norm)

def make_lognormal_from_interval(lb, ub, alpha):
    ''' Creates a lognormal distribution SciPy object from intervals.

    This function is a helper to create SciPy distributions by specifying the
    amount of wanted density between a lower and upper bound. For example,
    calling with (lb, ub, alpha) = (2, 3, 0.95) will create a LogNormal
    distribution with 95% density between 2 a 3.

    Args:
        lb (float): Lower bound
        ub (float): Upper bound
        alpha (float): Total density between lb and ub

    Returns:
        scipy.stats.lognorm

    Examples:
        >>> dist = make_lognormal_from_interval(2, 3, 0.95)
        >>> dist.mean()
        2.46262863041182
        >>> dist.std()
        0.25540947842844575
        >>> dist.interval(0.95)
        (1.9999999999999998, 2.9999999999999996)

    '''
    z = norm().interval(alpha)[1]
    mean_norm = np.sqrt(ub * lb)
    std_norm = np.log(ub / lb) / (2 * z)
    return lognorm(s=std_norm, scale=mean_norm)

class EmpiricalDistribution:
    def __init__(self, observations, method='sequential'):
        self.observations = np.array(observations)
        self.method = 'sequential'
        self.rvs = (self._sequential_rvs if method == 'sequential' else
                    self._uniform_rvs)

    def _sequential_rvs(self, size):
        assert size <= len(self.observations)
        return self.observations[:size]

    def _uniform_rvs(self, size):
        return np.random.choice(self.observations, size, replace=True)


import numpy as np

class PCA:

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

    def fit(self,X):
        #mean
        self.mean=np.mean(X,axis=0)
        X=X-self.mean

        #covariance
        cov = np.cov(X.T)
        #eigen values and eigen vectors
        eigenvalues, eigenvectors = np.linalg.eig(cov)

        #sort eigenvectors
        eigenvectors=eigenvectors.T
        idxs=np.argsort(eigenvalues)[::-1]
        eigenvalues=eigenvalues[idxs]
        eigenvectors=eigenvectors[idxs]

        # store first n eigenvectors
        self.components=eigenvectors[0:self.n_components]
        return eigenvalues[0:self.n_components],self.components,self.mean,cov
    def transform(self,X):
        # project data on vector found
        X=X-self.mean
        return np.dot(X,self.components.T)


import numpy as np
from numpy.linalg import inv,norm
import matplotlib.pyplot as plt

def solve_lambda_x(A,y,lambs_exponent=np.arange(-10,5,0.01)):
    '''
    Minimize the Lagrangian L(x,lambda) = || A*x - y||**2 + lambda*||x||**2 based on the idea of Tikhonov Regularization.
    This program is used to roughly estimate the parameters x and the Lagrange multiplier lambda.
    The L-curve method is applied to get the proper Lagrangian multiplier.

    Usage:
    estimate_lamb,estimate_x,log10_residual_norm,log10_solution_norm,curvature = solve_lambda_x(A,y)

    Inputs:
    A -> [float 2d array] Design matrix
    y -> [float array] Measurements

    Parameters:
    lambs_exponent -> [optional, float 3d/4d array, default = np.arange(-10,5,0.01)] Exponent for lambda with base of 10

    Outputs:
    estimate_lamb -> [float] Lagrange multiplier
    estimate_x -> [float array] Estimated parameters
    log10_residual_norm -> [float array] log10(||A*x-y||) with lambda taking 10**lambs_exponent
    log10_solution_norm -> [float array] log10(||x||) with lambda taking 10**lambs_exponent
    curvature -> [float array] curvature of the L-curve, where the ordinate of the curve is log10_solution_norm and the abscissa is log10_residual_norm.

    For more information, please refer to
    (1) [NumPy/SciPy Recipes for Data Science: Regularized Least Squares Optimization](https://www.researchgate.net/publication/274138835_NumPy_SciPy_Recipes_for_Data_Science_Regularized_Least_Squares_Optimization)
    (2) [Choosing the Regularization Parameter](http://www2.compute.dtu.dk/~pcha/DIP/chap5.pdf)
    '''
    np.seterr(divide='ignore',invalid='ignore')
    m = A.shape[1]

    # set a series of Lagrangian multiplier
    log10_residual_norm,log10_solution_norm = [],[]
    lambs = np.float_power(10,lambs_exponent)
    for lamb in lambs:
        x = np.dot(inv(np.dot(A.T,A)+lamb*np.eye(m)),np.dot(A.T,y))
        residual_norm = norm(np.dot(A,x)-y)
        solution_norm = norm(x)
        log10_residual_norm.append(np.log10(residual_norm))
        log10_solution_norm.append(np.log10(solution_norm))
    log10_residual_norm = np.array(log10_residual_norm)
    log10_solution_norm = np.array(log10_solution_norm)

    # calculate the curvature of the L-curve
    g1 = np.gradient(log10_solution_norm, log10_residual_norm)
    g1[np.isnan(g1)] = -np.inf
    g2 = np.gradient(g1,log10_residual_norm)
    g2[np.isnan(g2)] = np.inf
    curvature = np.abs(g2)/(1+g1**2)**1.5
    curvature[np.isnan(curvature)] = 0
    curvature[np.isinf(curvature)] = 0
    index_curvature_max = np.argmax(curvature)
    estimate_lamb = lambs[index_curvature_max]
    estimate_x = np.dot(inv(np.dot(A.T,A)+estimate_lamb*np.eye(m)),np.dot(A.T,y))
    return estimate_lamb,estimate_x,log10_residual_norm,log10_solution_norm,curvature

def L_curve(A,y,visible=None):
    '''
    Minimize the Lagrangian L(x,lambda) = || A*x - y||**2 + lambda*||x||**2 based on the idea of Tikhonov Regularization.
    This program is used to accurately estimate the parameters x and the Lagrange multiplier lambda.
    The final Lagrange multiplier is determained by the L-curve method. The L-curve can be visualized by outputing an image.

    Usage:
    accu_lamb,accu_x = L_curve(A,y)

    Inputs:
    A -> [float 2d array] Design matrix
    y -> [float array] Measurements

    Parameters:
    visible -> [optional, str, default = None] If None, the visualization of L-vurve will be closed. If 'visible', the L-curve will be visualized by outputing an image.

    Outputs:
    accu_lamb -> [float] Lagrange multiplier
    accu_x -> [float array] Estimated parameters

    For more information, please refer to
    (1) [NumPy/SciPy Recipes for Data Science: Regularized Least Squares Optimization](https://www.researchgate.net/publication/274138835_NumPy_SciPy_Recipes_for_Data_Science_Regularized_Least_Squares_Optimization)
    (2) [Choosing the Regularization Parameter](http://www2.compute.dtu.dk/~pcha/DIP/chap5.pdf)
    '''
    m = A.shape[1]

    # Estimate the Lagrange multiplier roughly
    appr_lamb,appr_x,log10_residual_norm,log10_solution_norm,appr_curvature = solve_lambda_x(A,y)

    # Estimate the Lagrange multiplier accurately
    lambs_exponent = np.linspace(np.log10(appr_lamb)-2,np.log10(appr_lamb)+2,2000)
    accu_lamb,accu_x,log10_residual_norm,log10_solution_norm,accu_curvature = solve_lambda_x(A,y,lambs_exponent)

    if visible is not None:
        fig_dir = 'figures/'
        if not os.path.exists(fig_dir): os.makedirs(fig_dir)
        # plot
        plt.clf()
        fig, (ax1, ax2) = plt.subplots(1, 2,dpi=200)
        # make a little extra space between the subplots
        fig.subplots_adjust(wspace=0.4)
        ax1.plot(log10_residual_norm,log10_solution_norm)
        ax1.set_xlabel(r'$\log \parallel A x_{\lambda}-y \parallel_2$')
        ax1.set_ylabel(r'$\log \parallel x_{\lambda} \parallel_2$')
        ax1.set_title('L-Curve')
        ax2.plot(lambs_exponent,accu_curvature)
        ax2.set_xlabel(r'$\log \parallel \lambda \parallel_2$')
        ax2.set_ylabel('Curvature')
        ax2.set_title('curvature of L-Curve')
        plt.savefig(fig_dir+'L-Curve.png')
    return accu_lamb,accu_x