import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Reading Data
data = pd.read_csv('AreaRent.csv')
print("DATASET \n",data)

# Collecting X and Y
X = data['Area'].values
Y = data['Rent'].values

# Mean X and Y
mean_x = np.mean(X)
mean_y = np.mean(Y)

# Total number of values
n = len(X)

# Using the formula to calculate m and c
numer = 0
denom = 0
for i in range(n):
    numer += (X[i] - mean_x) * (Y[i] - mean_y)
    denom += (X[i] - mean_x) ** 2
b1 = numer / denom
b0 = mean_y - (b1 * mean_x)

# Print coefficients
print("\nb1=" , b1 , "\nb0=" ,b0)

#Print Equation
print("Therefore, \nThe Linear Regression for given model is: y=",round(b1,3),"x+ (",round(b0,3),")")

#Finding Value of y
X1=input("Predicting Rent \n Enter Area(X): ")
y1= b1*float(X1) + b0
print("Area ", X1, "sq. ft has Rent of Rs. = ",y1)

# Plotting Values and Regression Line
max_x = np.max(X) + 5
min_x = np.min(X) - 5
# Calculating line values x and y
x = np.linspace(min_x, max_x)
y = b0 + b1 * x

# Ploting Line
plt.plot(x, y, c='#52b920', label='Regression Line')
# Ploting Scatter Points
plt.scatter(X, Y, c='#ef4423', label='Scatter Plot')
plt.xlabel('Area')
plt.ylabel('Rent')
plt.legend()
plt.show()


# Author: Jintao Huang
# Email: hjt_study@qq.com
# Date:

from numba import njit
import random
import math
from typing import Callable

@njit
def func(x: float) -> float:
    return math.sqrt(1 - x ** 2)

@njit
def HitorMiss(f: Callable[[float], float], n: int) -> float:
    """f: [0, 1]->[0, 1]"""
    k = 0  # 命中
    for _ in range(n):
        x = random.uniform(0, 1)
        y = random.uniform(0, 1)
        if y <= f(x):  # (x, y)在阴影处
            k += 1
    return k / n

pi = math.pi
print("精确解: %f" % pi)
#
print()
print("n 计算的pi值 误差")
for i in range(9):
    n = 10 ** i
    res = 4 * HitorMiss(func, n)
    print(n, res, abs(pi - res))

"""Out
精确解: 3.141593

n 计算的pi值 误差
1 4.0 0.8584073464102069
10 3.2 0.05840734641020706
100 3.16 0.018407346410207026
1000 3.188 0.04640734641020705
10000 3.1424 0.000807346410206744
100000 3.14836 0.006767346410206709
1000000 3.14018 0.0014126535897931447
10000000 3.1423656 0.0007729464102070871
100000000 3.14156932 2.3333589793228526e-05
"""


# Project Euler Problem 10
# 3/18/2013
# Copyright Shayne Hodge
# Find the sum of all the primes below two million.

import numpy

def sieve(start):
    if (start == 2):
        k = 2
        while (((k*start)-offset) <= (isprimelen-1)):
            isprime[(k*start) - offset] = 0
            k += 1
        return
    k = 3
    while (((k*start)-offset) <= (isprimelen-1)):
        isprime[(k*start) - offset] = 0
        k += 2
    return

def nextvalue(lastvalue):
    iindex = lastvalue - offset + 2
    while (iindex <= (end_value-offset)):
        if isprime[iindex] == 1:
            return (iindex + offset)
        else:
            iindex += 2
    return (end_value+1)

end_value = 2*10**6 - 1
sievelist = numpy.array(range(2, end_value+1),dtype='int64')
offset = sievelist[0]
isprime = numpy.array([1] * (end_value + 1 - offset),dtype='int64')
isprimelen = len(isprime)

sieve(2)
sieve(3)
knownprime = 5

while (knownprime <= end_value):
    sieve(knownprime)
    knownprime = nextvalue(knownprime)

primesum = sum(sievelist*isprime)

print('The sum of all primes less than', end_value+1, 'is', primesum, '.')


# -*- coding: utf-8 -*-#
'''
# Name:         Evaluation
# Description:  回归常用评价指标
# Author:       super
# Date:         2020/5/14
'''

import numpy as np
from sklearn import metrics

def MSE(y_true, y_pred):
    return np.average((y_true - y_pred) ** 2, axis=0)
    # return np.mean((y_pred - y_true) * (y_pred - y_true))

def RMSE(y_true, y_pred):
    return np.sqrt(np.average((y_true - y_pred) ** 2, axis=0))

def MAE(y_true, y_pred):
    return np.average(np.abs(y_pred - y_true), axis=0)

def MAPE(y_true, y_pred):
    return np.average(np.abs((y_pred - y_true) / y_true), axis=0) * 100

def SMAPE(y_true, y_pred):
    return 2.0 * np.average(np.abs(y_pred - y_true) / (np.abs(y_pred) + np.abs(y_true)), axis=0) * 100

if __name__ == '__main__':
    y_pred = np.array([1.0, 3.3, 2.0, 7.9, 5.5, 6.4, 2.0])
    y_true = np.array([2.0, 3.0, 2.5, 1.0, 4.0, 3.2, 3.0])
    print(metrics.mean_squared_error(y_true=y_true, y_pred=y_pred))
    print(MSE(y_true=y_true, y_pred=y_pred))

    print(np.sqrt(metrics.mean_squared_error(y_true=y_true, y_pred=y_pred)))
    print(RMSE(y_true=y_true, y_pred=y_pred))

    print(metrics.mean_absolute_error(y_true=y_true, y_pred=y_pred))
    print(MAE(y_true=y_true, y_pred=y_pred))


import numpy as np

def procrustes(X, Y, scaling=True, reflection='best'):
    """
    A port of MATLAB's `procrustes` function to Numpy.

    Procrustes analysis determines a linear transformation (translation,
    reflection, orthogonal rotation and scaling) of the points in Y to best
    conform them to the points in matrix X, using the sum of squared errors
    as the goodness of fit criterion.

        d, Z, [tform] = procrustes(X, Y)

    Inputs:
    ------------
    X, Y
        matrices of target and input coordinates. they must have equal
        numbers of  points (rows), but Y may have fewer dimensions
        (columns) than X.

    scaling
        if False, the scaling component of the transformation is forced
        to 1

    reflection
        if 'best' (default), the transformation solution may or may not
        include a reflection component, depending on which fits the data
        best. setting reflection to True or False forces a solution with
        reflection or no reflection respectively.

    Outputs
    ------------
    d
        the residual sum of squared errors, normalized according to a
        measure of the scale of X, ((X - X.mean(0))**2).sum()

    Z
        the matrix of transformed Y-values

    tform
        a dict specifying the rotation, translation and scaling that
        maps X --> Y

    """

    n,m = X.shape
    ny,my = Y.shape

    muX = X.mean(0)
    muY = Y.mean(0)

    X0 = X - muX
    Y0 = Y - muY

    ssX = (X0**2.).sum()
    ssY = (Y0**2.).sum()

    # centred Frobenius norm
    normX = np.sqrt(ssX)
    normY = np.sqrt(ssY)

    # scale to equal (unit) norm
    X0 /= normX
    Y0 /= normY

    if my < m:
        Y0 = np.concatenate((Y0, np.zeros(n, m-my)),0)

    # optimum rotation matrix of Y
    A = np.dot(X0.T, Y0)
    U,s,Vt = np.linalg.svd(A,full_matrices=False)
    V = Vt.T
    T = np.dot(V, U.T)

    if reflection is not 'best':

        # does the current solution use a reflection?
        have_reflection = np.linalg.det(T) < 0

        # if that's not what was specified, force another reflection
        if reflection != have_reflection:
            V[:,-1] *= -1
            s[-1] *= -1
            T = np.dot(V, U.T)

    traceTA = s.sum()

    if scaling:

        # optimum scaling of Y
        b = traceTA * normX / normY

        # standarised distance between X and b*Y*T + c
        d = 1 - traceTA**2

        # transformed coords
        Z = normX*traceTA*np.dot(Y0, T) + muX

    else:
        b = 1
        d = 1 + ssY/ssX - 2 * traceTA * normY / normX
        Z = normY*np.dot(Y0, T) + muX

    # transformation matrix
    if my < m:
        T = T[:my,:]
    c = muX - b*np.dot(muY, T)

    #transformation values
    tform = {'rotation':T, 'scale':b, 'translation':c}

    return d, Z, tform


from numpy import random, pi

Ntrials, Nhits = 1_000_000, 0
for n in range(Ntrials):
    x, y = random.uniform(-1, 1, 2) # draw 2 samples, each uniformly distributed over (-1,1)
    if (x**2 + y**2)**2 <= 2*abs(x*y):
        Nhits += 1
print("Monte Carlo estimator of Pi: %.5f" % (4 * Nhits / Ntrials))


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

# Program do znajdowania pierwiastków równania
# x**3 + x**2 - 3*x - 2 = 0
# w przedziałach [-3, -1] i [-1, 1]
# metodą bisekcji

import numpy as np

def f(u):
    '''Oblicza wartość funkcji wielomianu z zadania'''
    wartosc = u ** 3 + u ** 2 - 3 * u - 2

    return wartosc

print "Program do znajdowania pierwiastków równania\n"
print "x**3  +  x**2  - 3*x  - 2 = 0\n"
print "w przedziałach [-3, -1] i [-1, 1]"
print "metodą bisekcji."

# wartości ograniczeń przedziału
a = -3.0
b = -1.0
c = 1.0

N = int(raw_input("\nPodaj liczbę kroków "))

# obliczenie pierwiastków w przedziale [-3, -1]
# dolna granica przedziału
d = a

# górna granica przedziału
u = b

for licz in range (0,2):
    for k in range(0, N):
        # środek przedziału
        m = (d + u) / 2

        # oblicznie wartości funkcji wielomianu dla m i d
        wart_m = f(m)
        wart_d = f(d)

        # określenie znaku otrzymanych wartości
        znak_wartm = np.sign(wart_m)
        znak_wartd = np.sign(wart_d)

        #sprawdzenie czy wartość funkcji dla argumentu z środka przedziału jest równa 0
        # sprawdzenie czy wartości na końcach przedziałów mają ten sam znak
        # czyli nie funkcja w tym przedziale nie przechodzi przez osi OX
        if znak_wartm == znak_wartd:
            d = m

        # sprawdzenie czy wartości na końcach przedziałów mają przeciwne znaki
        # czyli funkcja w tym przedziale przechodzi przez osi OX
        if znak_wartm == ( znak_wartd * (-1) ):
            u = m

    #przedział, w którym znajduje się pierwiastek
    print "\nWyznaczony przedział, w którym znajduje się pierwiastek"
    print "[d, u] = ", "[", d, ",", u, "]"

    #wyznaczenie pierwiastka
    pierw = (d + u)/2
    wart_p= f(pierw)
    print "\nPierwiastek wynosi ", pierw
    print "f(",pierw,") = ", wart_p

    #zmian granic przedziału dla obliczenia pierwiastka w przedziale [-1,1]
    d = b
    u = c

#wyliczenie miary błędu
bl_1 = (b-a)/(2**N)
bl_2 = (c-b)/(2**N)

print "\nBłąd wyznaczenia pierwiastka w przedziale [-3,-1] wynosi ", bl_1
print "Błąd wyznaczenia pierwiastka w przedziale [-1,1]  wynosi ", bl_2


import logging
from functools import partial
from typing import Tuple, Callable

import numpy as np
from numpy.linalg import pinv
from src.local_optimization.local_optimizer import LocalOptimizer
from src.termination import check_n_iter, check_absolute_cost, check_n_iter_without_improvement
from src.math import gradient
from src.loss import mse

logger = logging.getLogger(__name__)

TERMINATION_CHECKS = (
    partial(check_n_iter, threshold=500),
    partial(check_absolute_cost, threshold=1e-6),
    partial(check_n_iter_without_improvement, threshold=20),
)

class LevenbergMarquardt(LocalOptimizer):
    """
    Levenberg-Marquardt (LMA) optimization method.

    Levenberg-Marquardt is damped Gauss-Newton method where increment vector is rotated towards the direction of
    steepest descent. Amount of this rotation is determined by damping factor which is changed during iteration.
    If cost decreases, the variables are updated and damping factor is decreased, making LMA to work more like
    standard Gauss-Newton. If cost increases, the variables are not updated and damping factor is increased,
    making LMA to work more like gradient descent.
    """

    def __init__(self,
                 f_err: Callable,
                 damping_increase_factor=2.0,
                 damping_decrease_factor=2.0,
                 termination_checks=TERMINATION_CHECKS
                 ):
        """
        @param f_err: Function to calculate errors: errors = f_err(x). cost is then calculated as MSE(errors).
        @param damping_increase_factor: Scale factor to increase damping factor (> 1).
        @param damping_decrease_factor: Scale factor to decrease damping factor (> 1).
        @param termination_checks: See LocalOptimizer.
        """
        self.f_err = f_err
        self.weights = None
        self.damping_increase_factor = damping_increase_factor
        self.damping_decrease_factor = damping_decrease_factor
        self.damping_factor = None
        super().__init__(self.f_cost, termination_checks)

    def update(self, x, iter_round, cost) -> Tuple[np.ndarray, float]:
        x_delta = self._calculate_update_delta(x)
        x_candidate = x - x_delta
        cost_candidate = self.f_cost(x_candidate)
        if cost_candidate < cost:
            x = x_candidate
            cost = cost_candidate
            self.damping_factor = self.damping_factor / self.damping_decrease_factor
            logger.debug(f"Cost decreased; decrease damping factor and update variables")
        else:
            self.damping_factor = self.damping_increase_factor * self.damping_factor
            logger.debug(f"Cost increased; increase damping factor and don't update variables")
        logger.debug(f"Cost {cost:0.3f}; damping factor {self.damping_factor:0.3f}")

        return x, cost

    def _calculate_update_delta(self, x) -> np.ndarray:
        errors = self.f_err(x)
        jac = gradient(x, self.f_err)
        jj = jac.T @ jac
        if self.damping_factor is None:
            self.damping_factor = np.max(np.diag(jj))
        correction = np.diag(self.damping_factor * np.diag(jj))
        return pinv(jj + correction) @ jac.T @ errors

    def f_cost(self, x):
        return mse(self.f_err(x))


import numpy as np  # do obliczeń trygonometrycznych oraz generowania argumentów funkcji do stworzenia wykresu
import sympy as sp  # do tworzenia wzorów funkcji z parametrem oraz obliczeń pochodnych
from horner import horner

def wartosc_funkcji(arg_x, wybor):
    """Zwracanie wartości wybranej funkcji dla argumentu x

            Parametry
            ----------
            arg_x : float
                wartosc argumentu x
            wybor  : String
                wybór funkcji A, B, C, D lub E
            Dane wyjsciowe
            -------
            wart_fun : float
                wartość funkcji dla argumentu x
    """
    if wybor == "A":    # A dla funkcji wielomianowej
        wart_fun = horner([5, 2, -1, 5], arg_x)  # obliczenie wartosci schematem hornera
    elif wybor == "B":  # B dla funkcji trygonometrycznej
        wart_fun = 5 * np.cos(arg_x) - 3 * np.sin(arg_x)
    elif wybor == "C":  # C dla modułu
        wart_fun = abs(arg_x - 5)
    elif wybor == "D":  # D dla liniowej
        wart_fun = arg_x - 5
    elif wybor == "E":  # D dla liniowej
        wart_fun = abs(np.cos(arg_x) - 0.5)
    else:
        print("Podano nieprawidlowa wartosc. Wybierz a, b, c lub d")
        wart_fun = None
    return wart_fun     # zwróc wartość funkcji dla konkretnego argumentu x

def wzor_funkcji(wybor):
    """Zwracanie wzoru funkcji z parametrem x

            Parametry
            ----------
            wybor  : String
                wybór funkcji A, B, C, D lub E
            Dane wyjsciowe
            -------
            wzor_fun : Symbol
                wzór funkcji zapisany ze zmienną x
    """
    arg_x = sp.Symbol('x')
    if wybor == "A":  # A dla funkcji wielomianowej
        wzor_fun = horner([5, 2, -1, 5], arg_x)  # obliczenie wartosci schematem hornera
    elif wybor == "B":  # B dla funkcji trygonometrycznej
        wzor_fun = 5 * sp.cos(arg_x) - 3 * sp.sin(arg_x)
    elif wybor == "C":  # C dla modułu
        wzor_fun = abs(arg_x - 5)
    elif wybor == "D":  # D dla liniowej
        wzor_fun = arg_x - 5
    elif wybor == "E":  # D dla liniowej
        wzor_fun = abs(sp.cos(arg_x) - 0.5)
    else:
        print("Podano nieprawidlowa wartosc. Wybierz a, b, c lub d")
        wzor_fun = None
    return wzor_fun     # zwroc wzór funkcji


#!/usr/bin/env python3
"""
Clustering Module
"""
import numpy as np

def maximization(X, g):
    """
    Calculates the maximization step in the EM algorithm for a GMM:

    X is a numpy.ndarray of shape (n, d) containing the data set
    g is a numpy.ndarray of shape (k, n) containing the posterior
    probabilities for each data point in each cluster
    You may use at most 1 loop

    Returns: pi, m, S, or None, None, None on failure
        pi is a numpy.ndarray of shape (k,) containing the updated priors for
        each cluster
        m is a numpy.ndarray of shape (k, d) containing the updated centroid
        means for each cluster
        S is a numpy.ndarray of shape (k, d, d) containing the updated
        covariance matrices for each cluster
    """
    if not isinstance(X, np.ndarray) or len(X.shape) != 2:
        return None, None, None
    if not isinstance(g, np.ndarray) or len(g.shape) != 2:
        return None, None, None
    if X.shape[0] != g.shape[1]:
        return None, None, None

    n, d = X.shape
    k, _ = g.shape

    if not np.isclose(np.sum(g, axis=0), np.ones((n, ))).all():
        return None, None, None
    pi, m, s = np.zeros((k,)), np.zeros((k, d)), np.zeros((k, d, d))

    for i in range(k):
        m[i] = np.dot(g[i], X) / np.sum(g[i])
        xmm = X - m[i]
        s[i] = np.dot(g[i] * xmm.T, xmm) / np.sum(g[i])
        pi[i] = np.sum(g[i]) / n
    return pi, m, s


'''
    Badea Adrian Catalin, grupa 334

    Sa se genereze prin doua metode variabila Beta(2, 4).
    Sa se genereze histogramele asociate celor doua metode

'''
from scipy.stats import gamma
from scipy.stats import uniform
import numpy as np

import matplotlib as mpl
import matplotlib.pyplot as plt

def plot_hist(b, title):

    pl, ax = plt.subplots(1, 1)

    ax.hist(b, density=True, histtype='stepfilled', alpha=0.2)

    ax.set_title(title)

    plt.show()

def plot_hist_algorithm(b, title):

    b_np = np.array(b)

    minim = np.min(b_np)
    maxim = np.max(b_np)
    k = 40

    x = np.linspace(minim, maxim, k)
    f = np.zeros(k)

    for i in range(0, len(b)):
        for j in range(0, k - 1):
            if x[j] <= b[i] and b[i] <= x[j + 1]:
                f[j] += 1

    r = np.zeros(k)
    for j in range(0, k - 1):
        r[j] = f[j] / len(b)

    pl, ax = plt.subplots(1, 1)

    ax.plot(x, r)

    ax.set_title(title)

def gamma_va(a, nr_sub):

    pl, ax = plt.subplots(1, 1)

    x = np.linspace(gamma.ppf(0.001, a), gamma.ppf(0.999, a), nr_sub)

    ax.plot(x, gamma.pdf(x, a), 'r-', lw=3, alpha=0.6, label='gamma pdf')

    ax.set_title('Gamma variable from 0 to 1 with a = {}'.format(a))

    return gamma.pdf(x, a)

def beta_va(a, b, nr_samples):

    gamma_va(a, nr_samples)
    gamma_va(b, nr_samples)

    g1 = gamma.rvs(a, size = nr_samples)

    g2 = gamma.rvs(b, size = nr_samples)

    b = []
    for i in range(0, nr_samples):
        if g1[i] + g2[i] > 0:
            b.append(g1[i] / (g1[i] + g2[i]))

    print('Beta from Gamma:')

    print('Mean: {}'.format(np.mean(np.array(b))))

    print('Standard deviation: {}'.format(np.std(np.array(b))))

    plot_hist(b, 'Beta calculated from gamma distributions')

    plot_hist_algorithm(b, 'Beta histogram from gamma')

def beta_u(a, b, nr_samples):

    n = a + b - 1
    b = []
    for i in range(0, nr_samples):

        u = uniform.rvs(size = n)
        u = sorted(u)
        b.append(u[a - 1])

    print('Beta from uniform:')

    print('Mean is {}'.format(np.mean(np.array(b))))

    print('Standard deviation is {}'.format(np.std(np.array(b))))

    plot_hist(b, 'Beta calculated from uniform distributions')

    plot_hist_algorithm(b, 'Beta histogram from uniform')

if __name__ == '__main__':

    a, b = 2, 4

    nr_samples = 1000000

    mpl.style.use('seaborn')

    print('Generation of Beta variable using Uniform and Gamma random variables for {} samples.'.format(nr_samples))

    print('Coefficients : a = {} and b = {}'.format(a, b))

    print('Theorical mean is a / (a + b) ')
    print('Expected mean : {}'.format(a / (a + b)))

    print('Theoretical variance is a * b / ((a + b) ^ 2 * (a + b + 1)')

    var = a * b / ((a + b) ** 2 * (a + b + 1))

    print('Expected variance: {}'.format(var))
    print('Expected standard deviation: {}'.format(np.sqrt(var)))

    beta_va(a, b, nr_samples)

    beta_u(a, b, nr_samples)


'''
Python Basics with Numpy
This is a brief introduction to Python.
The script uses Python 3.

The script below is to get familiar with:
1. Be able to use iPython Notebooks
2. Be able to use numpy functions and numpy matrix/vector operations
3. Understand the concept of "broadcasting"
4. Be able to vectorize code
Let's get started!
'''

#==============================================================================================================
#1 - Building basic functions with numpy
#==============================================================================================================

'''
Numpy is the main package for scientific computing in Python. It is maintained by a large community (www.numpy.org).Several key numpy functions such as np.exp, np.log, and np.reshape are used everyday in ML and DL.
'''
#------------------------------------------
# 1.1 - sigmoid function, np.exp()
#------------------------------------------
'''Before using np.exp(), we will use math.exp() to implement the sigmoid function. we will then see why np.exp() is preferable to math.exp().
Exercise: Build a function that returns the sigmoid of a real number x. Use math.exp(x) for the exponential function.
Reminder: sigmoid(x)=11+e−xsigmoid(x)=11+e−x is sometimes also known as the logistic function. It is a non-linear function used not only in Machine Learning (Logistic Regression), but also in Deep Learning.

To refer to a function belonging to a specific package we could call it using package_name.function().
'''

import math

def basic_sigmoid(x):
    """
    Compute sigmoid of x.

    Arguments:
    x -- A scalar

    Return:
    s -- sigmoid(x)
    """

    s = 1 / (1 + math.exp(-x))
    return s

#basic_sigmoid(3)	#0.9525741268224334
'''
Actually, we rarely use the "math" library in deep learning because the inputs of the functions are real numbers. In deep learning we mostly use matrices and vectors. This is why numpy is more useful.
### One reason why we use "numpy" instead of "math" in Deep Learning ###

'''
#Uncomment and run this line to see the error
#x = [1, 2, 3]
#basic_sigmoid(x)
'''
In fact, if x=(x1,x2,...,xn)x=(x1,x2,...,xn) is a row vector then np.exp(x)np.exp(x) will apply the exponential function to every element of x. The output will thus be: np.exp(x)=(ex1,ex2,...,exn)np.exp(x)=(ex1,ex2,...,exn)
'''
import numpy as np

# example of np.exp
x = np.array([1, 2, 3])
print(np.exp(x)) # result is (exp(1), exp(2), exp(3))

'''
Furthermore, if x is a vector, then a Python operation such as s=x+3s=x+3 or s=1xs=1x will output s as a vector of the same size as x.
'''
# example of vector operation
x = np.array([1, 2, 3])
print (x + 3)
#[4 5 6]
'''Any time we need more info on a numpy function, look at the official documentation or write np.exp? (for example) to get quick access to the documentation.
'''
import numpy as np # this means we can access numpy functions by writing np.function() instead of numpy.function()

def sigmoid(x):
    """
    Compute the sigmoid of x

    Arguments:
    x -- A scalar or numpy array of any size

    Return:
    s -- sigmoid(x)
    """

    s = 1 / (1 + np.exp(-x) )

    return s

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

#------------------------------------------
# 1.2 - Sigmoid gradient
#------------------------------------------
'''
we will need to compute gradients to optimize loss functions using backpropagation. Let's code our first gradient function.
Exercise: Implement the function sigmoid_grad() to compute the gradient of the sigmoid function with respect to its input x.
The formula is:

sigmoid_derivative(x)=σ′(x)=σ(x)(1−σ(x))
'''

def sigmoid_derivative(x):
    """
    Compute the gradient (also called the slope or derivative) of the sigmoid function with respect to its input x.
    we can store the output of the sigmoid function into variables and then use it to calculate the gradient.

    Arguments:
    x -- A scalar or numpy array

    Return:
    ds -- wer computed gradient.
    """

    s = sigmoid(x)
    ds = s*(1-s)

    return ds

#x = np.array([1, 2, 3])
#print ("sigmoid_derivative(x) = " + str(sigmoid_derivative(x)))

#------------------------------------------
# 1.3 - Reshaping arrays
#------------------------------------------
'''
Two common numpy functions used in deep learning are np.shape and np.reshape().
X.shape is used to get the shape (dimension) of a matrix/vector X.
X.reshape(...) is used to reshape X into some other dimension.
For example, in computer science, an image is represented by a 3D array of shape (length,height,depth=3)(length,height,depth=3). However, when we read an image as the input of an algorithm we convert it to a vector of shape (length∗height∗3,1)(length∗height∗3,1). In other words, we "unroll", or reshape, the 3D array into a 1D vector.

Let us implement image2vector() that takes an input of shape (length, height, 3) and returns a vector of shape (length*height*3, 1). For example, if we would like to reshape an array v of shape (a, b, c) into a vector of shape (a*b,c) we would do:
v = v.reshape((v.shape[0]*v.shape[1], v.shape[2])) # v.shape[0] = a ; v.shape[1] = b ; v.shape[2] = c
'''

def image2vector(image):
    """
    Argument:
    image -- a numpy array of shape (length, height, depth)

    Returns:
    v -- a vector of shape (length*height*depth, 1)
    """

    v = image.reshape(image.shape[0]*image.shape[1]*image.shape[2],1)

    return v

# This is a 3 by 3 by 2 array, typically images will be (num_px_x, num_px_y,3) where 3 represents the RGB values
image = np.array([[[ 0.67826139,  0.29380381],
        [ 0.90714982,  0.52835647],
        [ 0.4215251 ,  0.45017551]],

       [[ 0.92814219,  0.96677647],
        [ 0.85304703,  0.52351845],
        [ 0.19981397,  0.27417313]],

       [[ 0.60659855,  0.00533165],
        [ 0.10820313,  0.49978937],
        [ 0.34144279,  0.94630077]]])

print ("image2vector(image) = " + str(image2vector(image)))

#------------------------------------------
# 1.4 - Normalizing rows
#------------------------------------------
'''
Another common technique we use in Machine Learning and Deep Learning is to normalize our data. It often leads to a better performance because gradient descent converges faster after normalization. Here, by normalization we mean changing x to x/∥x∥ (dividing each row vector of x by its norm).

we can divide matrices of different sizes and it works fine: this is called broadcasting
Let us implement normalizeRows() to normalize the rows of a matrix. After applying this function to an input matrix x, each row of x should be a vector of unit length (meaning length 1).
'''

def normalizeRows(x):
    """
    Implement a function that normalizes each row of the matrix x (to have unit length).

    Argument:
    x -- A numpy matrix of shape (n, m)

    Returns:
    x -- The normalized (by row) numpy matrix. we are allowed to modify x.
    """

    # Compute x_norm as the norm 2 of x. Use np.linalg.norm(..., ord = 2, axis = ..., keepdims = True)
    x_norm = np.linalg.norm(x,axis=1,keepdims=True)

    # Divide x by its norm.
    x = x / x_norm

    return x

x = np.array([
    [0, 3, 4],
    [1, 6, 4]])
print("normalizeRows(x) = " + str(normalizeRows(x)))

'''
Note: In normalizeRows(), we can try to print the shapes of x_norm and x, and then rerun the assessment. we'll find out that they have different shapes. This is normal given that x_norm takes the norm of each row of x. So x_norm has the same number of rows but only 1 column. So how did it work when we divided x by x_norm? This is called broadcasting and we'll talk about it now!
'''
#------------------------------------------
# 1.5 - Broadcasting and the softmax function
#------------------------------------------
'''
A very important concept to understand in numpy is "broadcasting". It is very useful for performing mathematical operations between arrays of different shapes. For the full details on broadcasting, we can read the official broadcasting documentation.
Let us implement a softmax function using numpy. we can think of softmax as a normalizing function used when wer algorithm needs to classify two or more classes.
Read more about this at https://en.wikipedia.org/wiki/Softmax_function
'''

def softmax(x):
    """Calculates the softmax for each row of the input x.

    wer code should work for a row vector and also for matrices of shape (n, m).

    Argument:
    x -- A numpy matrix of shape (n,m)

    Returns:
    s -- A numpy matrix equal to the softmax of x, of shape (n,m)
    """

    # Apply exp() element-wise to x. Use np.exp(...).
    x_exp = np.exp(x)

    # Create a vector x_sum that sums each row of x_exp. Use np.sum(..., axis = 1, keepdims = True).
    x_sum = np.sum(x_exp,axis=1,keepdims=True)

    # Compute softmax(x) by dividing x_exp by x_sum. It should automatically use numpy broadcasting.
    s = x_exp/x_sum

    return s

x = np.array([
    [9, 2, 5, 0, 0],
    [7, 5, 0, 0 ,0]])
print("softmax(x) = " + str(softmax(x)))
'''
If we print the shapes of x_exp, x_sum and s above and rerun the assessment cell, we will see that x_sum is of shape (2,1) while x_exp and s are of shape (2,5). x_exp/x_sum works due to python broadcasting.
'''

#==============================================================================================================
# 2) Vectorization
#==============================================================================================================
'''
In deep learning, we deal with very large datasets. Hence, a non-computationally-optimal function can become a huge bottleneck in wer algorithm and can result in a model that takes ages to run. To make sure that wer code is computationally efficient, we will use vectorization. Below are a few examples that demonstarte this
'''
import time

x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]

### CLASSIC DOT PRODUCT OF VECTORS IMPLEMENTATION ###
tic = time.process_time()
dot = 0
for i in range(len(x1)):
    dot+= x1[i]*x2[i]
toc = time.process_time()
print ("dot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### CLASSIC OUTER PRODUCT IMPLEMENTATION ###
tic = time.process_time()
outer = np.zeros((len(x1),len(x2))) # we create a len(x1)*len(x2) matrix with only zeros
for i in range(len(x1)):
    for j in range(len(x2)):
        outer[i,j] = x1[i]*x2[j]
toc = time.process_time()
print ("outer = " + str(outer) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### CLASSIC ELEMENTWISE IMPLEMENTATION ###
tic = time.process_time()
mul = np.zeros(len(x1))
for i in range(len(x1)):
    mul[i] = x1[i]*x2[i]
toc = time.process_time()
print ("elementwise multiplication = " + str(mul) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### CLASSIC GENERAL DOT PRODUCT IMPLEMENTATION ###
W = np.random.rand(3,len(x1)) # Random 3*len(x1) numpy array
tic = time.process_time()
gdot = np.zeros(W.shape[0])
for i in range(W.shape[0]):
    for j in range(len(x1)):
        gdot[i] += W[i,j]*x1[j]
toc = time.process_time()
print ("gdot = " + str(gdot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]

### VECTORIZED DOT PRODUCT OF VECTORS ###
tic = time.process_time()
dot = np.dot(x1,x2)
toc = time.process_time()
print ("dot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### VECTORIZED OUTER PRODUCT ###
tic = time.process_time()
outer = np.outer(x1,x2)
toc = time.process_time()
print ("outer = " + str(outer) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### VECTORIZED ELEMENTWISE MULTIPLICATION ###
tic = time.process_time()
mul = np.multiply(x1,x2)
toc = time.process_time()
print ("elementwise multiplication = " + str(mul) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### VECTORIZED GENERAL DOT PRODUCT ###
tic = time.process_time()
dot = np.dot(W,x1)
toc = time.process_time()
print ("gdot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

'''
As you may have noticed, the vectorized implementation is much cleaner and more efficient. For bigger vectors/matrices, the differences in running time become even bigger.
Note that np.dot() performs a matrix-matrix or matrix-vector multiplication. This is different from np.multiply() and the * operator (which is equivalent to  .* in Matlab/Octave), which performs an element-wise multiplication.
'''
#------------------------------------------
# 2.1 Implement the L1 and L2 loss functions
#------------------------------------------
'''
Let us implement the numpy vectorized version of the L1 loss. we may find the function abs(x) (absolute value of x) useful.
The loss is used to evaluate the performance of wer model. The bigger wer loss is, the more different wer predictions (ŷ y^) are from the true values (y). In deep learning, we use optimization algorithms like Gradient Descent to train our model and to minimize the cost.
L1 loss is defined as:
L1(ŷ ,y)=∑i=0m|y(i)−ŷ (i)
'''

def L1(yhat, y):
    """
    Arguments:
    yhat -- vector of size m (predicted labels)
    y -- vector of size m (true labels)

    Returns:
    loss -- the value of the L1 loss function defined above
    """

    loss = np.sum(abs(yhat-y))

    return loss

yhat = np.array([.9, 0.2, 0.1, .4, .9])
y = np.array([1, 0, 0, 1, 1])
print("L1 = " + str(L1(yhat,y)))

'''
Let us also implement the numpy vectorized version of the L2 loss. There are several way of implementing the L2 loss but we may find the function np.dot() useful. As a reminder, if x=[x1,x2,...,xn]x=[x1,x2,...,xn], then np.dot(x,x) = ∑nj=0x2j∑j=0nxj2.
L2 loss is defined as
L2(ŷ ,y)=∑i=0m(y(i)−ŷ (i))2
'''

def L2(yhat, y):
    """
    Arguments:
    yhat -- vector of size m (predicted labels)
    y -- vector of size m (true labels)

    Returns:
    loss -- the value of the L2 loss function defined above
    """

    loss = np.sum(np.dot(yhat-y,yhat-y))
    #Also possible loss = np.sum((yhat-y)**2)

    return loss

yhat = np.array([.9, 0.2, 0.1, .4, .9])
y = np.array([1, 0, 0, 1, 1])
print("L2 = " + str(L2(yhat,y)))

# BONUS :  A quick reshape hack is to use -1 as the parameter
a = np.random.rand(3,2,2,3)
b = a.reshape(3,-1).T
'''
The shape of b is converted to (3,12). The number of columns is calculated automatically.
'''


'''
This problem was asked by Facebook.

Given a stream of elements too large to store in memory, pick a random element from the stream with
uniform probability.

SOLUTION: Reservoir Sampling:
    probability of selecting the 1st element after seeing only one element 100%
    probability of selecting the 2nd element after seeing only two elements 1/2
    probability of selecting the 3rd element after seeing only three elements 1/3
    probability of selecting the 4th element after seeing only four elements 1/4
    probability of selecting the 5th element after seeing only five elements 1/5

    Proof uniform sampling
    so probability of selecting the 1st element after seeing five elements is:
    1 * (1 - 1/2) * (1 - 1/3) * (1 - 1/4) * (1 - 1/5)
  = 1 * 1/2 * 2/3 * 3/4 * 4/5 = 1/5
'''

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

def uniform_sample(big_stream, printAction=True):
    random_element = None

    for index, element in enumerate(big_stream):

        if index == 0:
            if printAction:
                print("picked element {} from stream of {} elements".format(element, index + 1))
            random_element = element
        elif random.randint(1, index+1) == 1:
            if printAction:
                print("picked element {} from stream of {} elements".format(element, index + 1))
            random_element = element

    return random_element

def create_hist(big_stream, no_of_samples = 5000):
    hist = []

    for i in range(no_of_samples):
        hist.append(uniform_sample(big_stream, printAction=False))

    plt.hist(np.array(hist), align='left')
    plt.ylabel('Probability')
    plt.show()

if __name__ == '__main__':
    random.seed(1)
    big_stream = [1, 2, 3, 4, 5, 6]
    #print(uniform_sample(big_stream))
    create_hist(big_stream, no_of_samples=10000)


#!/usr/bin/env
# -*- coding: utf-8 -*-
'''
Script Name     : Gauss_Newton
Author          : Charles Young
Python Version  : Python 3.6.3
Requirements    : (Please check document: requirements.txt or use command "pip install -r requirements.txt")
Date     		: 2018-11-05
'''

import numpy as np
import os
import sys
import argparse
from scipy import io
import matplotlib.pyplot as plt

# If run in Windows, comment this line.
# plt.switch_backend('agg')

def get_args():
	parser = argparse.ArgumentParser()
	parser.add_argument('--maxits', default = 256, type = np.int, nargs = '?', help = "Maximum number of iterations of the algorithm to perform. Default 256.")
	parser.add_argument('--seed', dest = 'random_seed', default = 0, type = np.int, nargs = '?', help = "Random seed. Default 0")
	parser.add_argument('--num_init', default = 10, type = np.int, nargs = '?', help = "Number of initial guesses. Default 10.")
	parser.add_argument('--orth', dest = 'orthogonal', action = 'store_const', const = True, help = "Whether to orthogonalize the initial guesses.")
	parser.add_argument('--result', dest = 'save_result', action = 'store_const', const = True, help = "Whether to save the results.")
	parser.add_argument('--legend', dest = 'show_legend', action = 'store_const', const = True, help = "Whether to show the legend in error graph.")
	parser.add_argument('--plot', dest = 'show_graph', action = 'store_const', const = True, help = "Whether to show the error graph.")
	return parser.parse_args()

def calc_err(X, y, R):
	# Calculate the standard deviation.
	err = y - np.dot(R, X)
	return np.trace(np.dot(err.T, err))

def solve(X, y, R0, tol = 1e-10, maxits = 256):
	"""Gauss-Newton algorithm.
	Parameters
	----------
	X : ndarray
		Original vectors.
	y : ndarray
		Target vectors after rotation.
	R0 : ndarray
		Initial guesses or starting estimates.
	tol : float
	    Tolerance threshold. The problem is considered solved when this value
	    becomes larger than the change of the correction vector.
	    Defaults to 1e-10.
	maxits : int
	    Maximum number of iterations of the algorithm to perform.
	    Defaults to 256.
	Return
	------
	Rn : ndarray
	    Resultant values. In this problem, the rotation matrix.
	err : float
		The standard deviation.
	its : int
	    Number of iterations performed.
	errs : list
		The standard deviations during iteration.
	"""

	dx = np.ones(len(R0))   # Correction vector
	Rn = np.array(R0)       # Approximation of solution
	errs = []

	def skew(u):
		# Span a (3,1) vector into its corresponding (3,3) skew symmetric matrix.

		return np.array([[0, -u[2], u[1]], [u[2], 0, -u[0]], [-u[1], u[0], 0]])

	def calc_step(X, y, Rn):
		# Solve function dx = inv(J'J)J'z.

		# Calculate Jacobian matrix.
		p = np.dot(Rn, X)
		# For independent errors the whole Jacobian is just the stacked matrix of individual Jacobians.
		J = np.vstack([-skew(pi) for pi in p.T])

		# Calculate error.
		err = y - p
		# For independent errors the whole error is just the stacked vector of individual errors.
		z = np.hstack([col for col in (y - p).T])

		return np.dot(np.linalg.inv(np.dot(J.T, J)), np.dot(J.T, z))

	its = 0
	while (its < maxits) and (dx[np.abs(dx) > tol].size > 0):
		# Calculate correction vector.
		dx = calc_step(X, y, Rn)
		# Update R.
		Rn = Rn + np.dot(skew(dx), Rn) # Adjust R.
		U,__,VT = np.linalg.svd(Rn)    # Apply SVD decomposition.
		Rn = np.dot(U, VT)             # Obtain the nearest rotation matrix.

		assert (Rn.T - np.linalg.inv(Rn) < 1e-10).all()	# Check if orthogonal.
		assert np.linalg.det(Rn) - 1 < 1e-10            # Check if its det is 1.

		# Update iteration.
		its += 1

		err = calc_err(X, y, Rn)
		errs.append(err)
		# print('  Iter: {0}, Error: {1}'.format(its, calc_err(X, y, Rn)))

	return Rn, err, its, errs

if __name__ == '__main__':

	args = get_args()

	np.random.seed(args.random_seed)

	# Load data.
	data = io.loadmat('./data/xy.mat')
	X, y = data['x'], data['y']

	# Solve the equation directly.
	sol = np.dot(y, np.linalg.inv(X))
	err = calc_err(X, y, sol)
	print("  Solution      : \r\n{}".format(sol))
	print("  Error         : {}".format(err))
	print('-'*50)
	if args.save_result:
		io.savemat('./result/solution0.mat', {'R':sol})

	# Generate some initial guesses.
	starts = []
	for i in range(args.num_init):
		R = np.random.randn(9).reshape((3,3)).astype('float64')
		if args.orthogonal:
			U,__,VT = np.linalg.svd(R)
			R = np.dot(U, VT)
		starts.append(R)

	if not os.path.exists('./result/'):
		os.makedirs('./result/')

	# Perform Gauss-Newton algprithm on each initial guess.
	errs_all = []
	min_err = err
	best_sol = sol
	for i, R0 in enumerate(starts):
		print("Start {}:".format(i + 1))
		print("  Initial guess : \r\n{}".format(R0))
		sol, err, its, errs = solve(X, y, R0, maxits = args.maxits)
		print("  Iterations    : {}".format(its))
		print("  Solution      : \r\n{}".format(sol))
		print("  Error         : {}".format(err))
		print('-'*50)
		errs_all.append(errs)
		if err < min_err:
			min_err = err
			best_sol = sol

	print("  Best Solution : \r\n{}".format(best_sol))
	print("  Error         : {}".format(min_err))
	print('-'*50)
	if args.save_result:
		io.savemat('./result/solution_best.mat', {'R':best_sol})

	# Display
	fig = plt.figure(figsize = (19.2,10.8))
	plt.title('Error')
	for i in range(len(starts)):
		plt.plot(np.arange(len(errs_all[i])), errs_all[i], label = 'Guess '+ str(i + 1))
	if args.show_legend:
		plt.legend()

	# Save graph to file.
	plt.savefig('./result/error' + str(len(starts)) + '{}'.format(args.orthogonal) + '.svg', bbox_inches = 'tight', format = 'svg')
	if args.show_graph:
		plt.show()


# -*- coding: utf-8 -*-
"""
Created on Sun Apr 12 10:36:53 2020

@author: Abdeljalil
"""

import numpy as np
import matplotlib.pyplot as plt

plt.rc('font', family='serif')
plt.rc('xtick', labelsize='10')
plt.rc('ytick', labelsize='10')
plt.rc('axes', titlesize=12)
plt.rc('text', usetex=True)
#%%

freq = 100
T = 1./freq
N = 300

x = np.linspace(0,N*T, N)
signal = np.sin(2*np.pi*5*x) + 3*np.sin(2*np.pi*15*x) + 2*np.sin(2*np.pi*30*x)+ np.random.randn(N)

#%% Plot FFT
fig, axes = plt.subplots(1,2,figsize=(6,2.5))
axes[0].plot(x, signal, c='k', lw=1.4)
axes[0].set_xticks([0,1,2,3])
axes[0].set_xlabel('Time (s)')
axes[0].set_yticklabels([])
axes[0].set_ylabel('Amplitude')
axes[0].grid(True)
axes[0].set_title('Time domain')

fft_y = np.fft.fft(signal)
fft_y = abs(fft_y[:N//2])
fft_x = np.linspace(0,freq/2,N//2)

axes[1].plot(fft_x, (2/N)*fft_y, c='k', lw=1)
axes[1].set_xlabel('Frequency (Hz)')
axes[1].set_ylabel('Amplitude')
axes[1].set_yticklabels([])
axes[1].set_ylim([0,3])
axes[1].grid(True)
axes[1].set_title('Frequency domain')
fig.tight_layout()
fig.savefig('/home/abdeljalil/Workspace/MasterThesis/figures/fft.pdf')

#%% Plot FFT [French]
fig, axes = plt.subplots(1,2,figsize=(6,2.5))
axes[0].plot(x, signal, c='k', lw=1.4)
axes[0].set_xticks([0,1,2,3])
axes[0].set_xlabel('Temps (s)')
axes[0].set_yticklabels([])
axes[0].set_ylabel('Amplitude')
axes[0].grid(True)
axes[0].set_title('Domaine temporel')

fft_y = np.fft.fft(signal)
fft_y = abs(fft_y[:N//2])
fft_x = np.linspace(0,freq/2,N//2)

axes[1].plot(fft_x, (2/N)*fft_y, c='k', lw=1)
axes[1].set_xlabel('Fréquence (Hz)')
axes[1].set_ylabel('Amplitude')
axes[1].set_yticklabels([])
axes[1].set_ylim([0,3])
axes[1].grid(True)
axes[1].set_title('Domaine fréquentiel')
fig.tight_layout()
fig.savefig('/home/abdeljalil/Workspace/MasterThesis/figures/fft_fr.pdf')


import numpy as np

def sigmoid(x):
    """Sigmoid function.
       @param x: input matrix/vector.
       @return: elementwise sigmoid"""
    return 1/(1+np.exp(-x))

def sigmoid_dash(x):
    """Sigmoid derivative function.
       @param x: input matrix/vector.
       @return: elementwise sigmoid derivative"""
    return sigmoid(x)*(1-sigmoid(x))

def relu(x):
    """Rectified linear activation function.
       @param x: input matrix/vector.
       @return: elementwise relu"""
    return np.maximum(x,0)

def relu_dash(x):
    """Relu derivative function.
       @param x: input matrix/vector.
       @return: elementwise relu derivative"""
    x[x<=0]=0
    x[x>0]=1
    return x

def softmax(x):
    """Softmax function.
       @param x: input matrix/vector.
       @return: columnwise softmax"""
    x_exp = np.exp(x)
    x_sum = np.sum(x_exp,axis=0)
    return x_exp/x_sum


import numpy as np

def find_num_solutions(A_pass, b_pass):
    """
        Using row reduction, determines the number of solutions that satisfy
        the linear system of equations given by A_pass.X = b_pass mod 2.
        Returns zero if no solutions exist.

    """
    A = np.copy(A_pass)
    b = np.copy(b_pass)

    M, N = A.shape

    h = 0
    k = 0

    while h<M and k<N:

        max_i = h

        for i in range(h, M):
            if A[i, k] == 1:
                max_i = i
                break

        if A[max_i, k] == 0:
            k += 1
        else:
            if h != max_i:
                A[[h, max_i]] = A[[max_i, h]]
                b[[h, max_i]] = b[[max_i, h]]

            for u in range((h+1), M):
                flip_val = A[u, k]
                A[u] = ( A[u] + flip_val*A[h] ) % 2
                b[u] = ( b[u] + flip_val*b[h] ) % 2

            h += 1
            k += 1

    # Find rows with all zeros
    num_all_zeros_rows = 0

    solutions_exist = True

    for i in range(M):
        if not np.any(A[i]): # All-zero row encountered

            if b[i] != 0:
                solutions_exist = False
                break

            num_all_zeros_rows += 1

    if solutions_exist:
        rank = M - num_all_zeros_rows
        num_solutions = np.power(2, N-rank)
    else:
        num_solutions = 0

    return num_solutions

def regular_xorsat(k, n):
    """
    Generates a k-regurlar k-XORSAT instance with n variables.

    """

    indices = np.zeros((n, k), dtype=int)

    while True:
        for i in range(k):
            indices[:, i] = np.random.permutation(n)

        if all(np.unique(row).size == k for row in indices):
            break

    b = np.random.choice(2, n)

    return indices, b

def plant_regular_xorsat(k, n):
    """
    Repetitively generates k-regular k-XORSAT problems until an instance
    with non-zero solutions is obtained.
    Returns the resultant XORSAT instance formulated as an
    Ising minimization problem.
    Also returns the ground state energy and ground state degenaracy.

    """

    while True:
        indices, b = regular_xorsat(k, n)

        A = np.zeros( (n, n), dtype=int )

        for i, row in enumerate(indices):
            A[i, row] = 1

        num_solutions = find_num_solutions(A, b)

        if num_solutions != 0:
            break

    gs_energy = -n

    bonds = []

    for i in range(n):
        bonds.append( tuple(indices[i]) + (-np.power(-1, b[i]), ) )

    return bonds, gs_energy, num_solutions


import math

import numpy as np
from scipy.fftpack import dct, idct

import matplotlib.pyplot as plt

# 离散余弦变换
def my_dct(x):
    assert x.ndim == 1
    size = x.shape[0]
    res = np.zeros(size)
    for i in range(size):
        for j in range(size):
            if i == 0:
                res[i] += math.sqrt(1.0 / size) * x[j] * math.cos((2 * j + 1) * math.pi * i / (2 * size))
            else:
                res[i] += math.sqrt(2.0 / size) * x[j] * math.cos((2 * j + 1) * math.pi * i / (2 * size))
    return res

def my_idct(x):
    assert x.ndim == 1
    size = x.shape[0]
    res = np.zeros(size)
    for i in range(size):
        for j in range(size):
            if j == 0:
                res[i] += math.sqrt(1.0 / size) * x[j] * math.cos((2 * i + 1) * math.pi * j / (2 * size))
            else:
                res[i] += math.sqrt(2.0 / size) * x[j] * math.cos((2 * i + 1) * math.pi * j / (2 * size))
    return res

if __name__ == '__main__':
    x = np.array([1, 2, 3, 5, 7])
    # x = np.random.uniform(low=0,high=2,size=1024)
    x = np.random.normal(loc=0,scale=10, size=102400)
    dct2 = dct(x, norm='ortho')
    idct2 = idct(dct2, norm='ortho')
    print("dct2", dct2)
    print("dct2 sum", sum(dct2))
    print("dct2 sum2", sum(dct2**2))
    print("idct2", idct2)

    x1 = range(102400)

    plt.plot(x1,dct2)
    plt.show()
    my_dct2 = my_dct(x)
    my_idct2 = my_idct(my_dct2)
    print("my_dct2", my_dct2)
    print("my_idct2", my_idct2)


import numpy as np

def cross_entropy_error(y, t):
    delta = 1e-7
    return -np.sum(t*np.log(y + delta))

t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
print(cross_entropy_error(np.array(y), np.array(t)))

y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
print(cross_entropy_error(np.array(y), np.array(t)))


import numpy as np
import cv2

def masking(A, kernel):
    value = 0
    for i in range(A.shape[0]):
        for j in range(A.shape[1]):
            value += (A[i, j] * kernel[i, j])
    return value

def laplacian1(im):
    ext_im = cv2.copyMakeBorder(im, 1, 1, 1, 1, cv2.BORDER_REFLECT)
    ext_im = np.asarray(a=ext_im, dtype=np.int)
    kernel = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]])
    lap = np.zeros(im.shape, np.int)
    for i in range(1, ext_im.shape[0] - 1):
        for j in range(1, ext_im.shape[1] - 1):
            lap[i-1][j-1] = masking(ext_im[i-1:i+2, j-1:j+2], kernel)
    return lap

def laplacian2(im):
    ext_im = cv2.copyMakeBorder(im, 1, 1, 1, 1, cv2.BORDER_REFLECT)
    ext_im = np.asarray(a=ext_im, dtype=np.int)
    kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]) / 3
    lap = np.zeros(im.shape, np.float32)
    for i in range(1, ext_im.shape[0] - 1):
        for j in range(1, ext_im.shape[1] - 1):
            lap[i-1][j-1] = masking(ext_im[i-1:i+2, j-1:j+2], kernel)
    return lap

def laplacian_min(im):
    ext_im = cv2.copyMakeBorder(im, 1, 1, 1, 1, cv2.BORDER_REFLECT)
    ext_im = np.asarray(a=ext_im, dtype=np.int)
    kernel = np.array([[2, -1, 2], [-1, -4, -1], [2, -1, 2]]) / 3
    lap = np.zeros(im.shape, np.float32)
    for i in range(1, ext_im.shape[0] - 1):
        for j in range(1, ext_im.shape[1] - 1):
            lap[i-1][j-1] = masking(ext_im[i-1:i+2, j-1:j+2], kernel)
    return lap

def laplacian_gauss(im):
    ext_im = cv2.copyMakeBorder(im, 5, 5, 5, 5, cv2.BORDER_REPLICATE)
    ext_im = np.asarray(a=ext_im, dtype=np.int)
    kernel = np.array([[0, 0, 0, -1, -1, -2, -1, -1, 0, 0, 0],
                       [0, 0, -2, -4, -8, -9, -8, -4, -2, 0, 0],
                       [0, -2, -7, -15, -22, -23, -22, -15, -7, -2, 0],
                       [-1, -4, -15, -24, -14, -1, -14, -24, -15, -4, -1],
                       [-1, -8, -22, -14, 52, 103, 52, -14, -22, -8, -1],
                       [-2, -9, -23, -1, 103, 178, 103, -1, -23, -9, -2],
                       [-1, -8, -22, -14, 52, 103, 52, -14, -22, -8, -1],
                       [-1, -4, -15, -24, -14, -1, -14, -24, -15, -4, -1],
                       [0, -2, -7, -15, -22, -23, -22, -15, -7, -2, 0],
                       [0, 0, -2, -4, -8, -9, -8, -4, -2, 0, 0],
                       [0, 0, 0, -1, -1, -2, -1, -1, 0, 0, 0]])
    lap = np.zeros(im.shape, np.float32)
    for i in range(5, ext_im.shape[0] - 5):
        for j in range(5, ext_im.shape[1] - 5):
            lap[i-5][j-5] = masking(ext_im[i-5:i+6, j-5:j+6], kernel)
    return lap

def laplacian_dif(im):
    ext_im = cv2.copyMakeBorder(im, 5, 5, 5, 5, cv2.BORDER_REPLICATE)
    ext_im = np.asarray(a=ext_im, dtype=np.int)
    kernel = np.array([[-1, -3, -4, -6, -7, -8, -7, -6, -4, -3, -1],
                       [-3, -5, -8, -11, -13, -13, -13, -11, -8, -5, -3],
                       [-4, -8, -12, -16, -17, -17, -17, -16, -12, -8, -4],
                       [-6, -11, -16, -16, 0, 15, 0, -16, -16, -11, -6],
                       [-7, -13, -17, 0, 85, 160, 85, 0, -17, -13, -7],
                       [-8, -13, -17, 15, 160, 283, 160, 15, -17, -13, -8],
                       [-7, -13, -17, 0, 85, 160, 85, 0, -17, -13, -7],
                       [-6, -11, -16, -16, 0, 15, 0, -16, -16, -11, -6],
                       [-4, -8, -12, -16, -17, -17, -17, -16, -12, -8, -4],
                       [-3, -5, -8, -11, -13, -13, -13, -11, -8, -5, -3],
                       [-1, -3, -4, -6, -7, -8, -7, -6, -4, -3, -1]])
    lap = np.zeros(im.shape, np.float32)
    for i in range(5, ext_im.shape[0] - 5):
        for j in range(5, ext_im.shape[1] - 5):
            lap[i-5][j-5] = masking(ext_im[i-5:i+6, j-5:j+6], kernel)
    return lap

def threshold(G, threshold):
    result = G
    for i in range(G.shape[0]):
        for j in range(G.shape[1]):
            if G[i][j] >= threshold:
                result[i][j] = 1
            elif G[i][j] <= -threshold:
                result[i][j] = -1
            else:
                result[i][j] = 0
    return result

def zerocross(result):
    ext_im = cv2.copyMakeBorder(result, 1, 1, 1, 1, cv2.BORDER_REFLECT)
    ext_im = np.asarray(a=ext_im, dtype=np.int)
    cross = False
    ans = np.zeros(result.shape, np.int)
    for i in range(1, ext_im.shape[0] - 1):
        for j in range(1, ext_im.shape[1] - 1):
            cross = zerocross_helper(ext_im[i-1:i+2, j-1:j+2])
            if cross:
                ans[i-1][j-1] = 0
            else:
                ans[i-1][j-1] = 255
    return ans

def zerocross_helper(square):
    if square[1, 1] >= 1:
        for i in range(square.shape[0]):
            for j in range(square.shape[1]):
                if square[i, j] <= -1:
                    return True
        return False
    else:
        return False

im = cv2.imread('lena.bmp', 0)
laplacian_1 = zerocross(threshold(laplacian1(im), 15))
cv2.imwrite('laplacian_1.bmp', laplacian_1)
laplacian_2 = zerocross(threshold(laplacian2(im), 15))
cv2.imwrite('laplacian_2.bmp', laplacian_2)
laplacian_min = zerocross(threshold(laplacian_min(im), 20))
cv2.imwrite('laplacian_min.bmp', laplacian_min)
laplacian_gauss = zerocross(threshold(laplacian_gauss(im), 3000))
cv2.imwrite('laplacian_gauss.bmp', laplacian_gauss)
laplacian_dif = zerocross(threshold(laplacian_dif(im), 1))
cv2.imwrite('laplacian_dif.bmp', laplacian_dif)


import numpy as np
import matplotlib.pyplot as plt
from math import sqrt
from scipy import stats
from scipy.stats import kurtosis, skew

def pdf(costh, P_mu):
    # define our probability density function
    return 0.5 * (1.0 - 1.0 / 3.0 * P_mu * costh)

def inv_cdf_pos(r, P_mu):
    # inverse of the cumulative density function
    # since we have positive and negative solutions for x(r), we have to consider both
    return 3./P_mu*(1.+np.sqrt(1.+2./3.*P_mu*(P_mu/6.+1.-2.*r)))

def inv_cdf_neg(r, P_mu):
    return 3./P_mu*(1.-np.sqrt(1.+2./3.*P_mu*(P_mu/6.+1.-2.*r)))

def inv_trans(N_measurements, P_mu):
    r = np.random.uniform(0.0, 1.0, size=N_measurements)
    data_pdf = inv_cdf_neg(r, P_mu)
    return data_pdf

def acc_rej(N_measurements, P_mu):
    x = np.random.uniform(-1.0, 1.0, size=N_measurements)

    x_axis = np.linspace(-1.0,1.0, 1000)
    fmax= np.amax(pdf(x_axis,P_mu)) #find the maximum of the function

    u = np.random.uniform(0, fmax, size= N_measurements)

    # we use a mask in order to reject the values we don't want
    data_pdf = x[u < pdf(x,P_mu)]
    return data_pdf

def t_variable(N_events, sample_mean, mean, unbiased_sample_mean):
    return sqrt(N_events)*(sample_mean-mean)/sqrt(unbiased_sample_mean)

def g_variable(N_events, sample_mean, mean, biased_sample_mean):
    return sqrt(N_events)*(sample_mean-mean)/sqrt(biased_sample_mean)

t = np.array([])
g = np.array([])
N_experiments = 0
N_experiments_max = 10000
N_measurements = 1000
P_mu=0.5
mean = -P_mu/9.

while N_experiments < N_experiments_max:
    data_pdf = inv_trans(N_measurements, P_mu) #the inv_trans methods works quite better because takes all the points
    sample_mean = np.mean(data_pdf)
    unbiased_sample_mean = np.var(data_pdf, ddof=1)
    biased_sample_mean = np.var(data_pdf, ddof=0)
    t = np.append(t, t_variable(N_measurements, sample_mean, mean, unbiased_sample_mean))
#    g = np.append(g, g_variable(N_measurements, sample_mean, mean, biased_sample_mean))
    N_experiments += 1
    print(N_experiments)

plt.hist(t, bins=30, normed=True, histtype="step", color="black", linewidth="1")
plt.xlabel(r't')
plt.ylabel(r'$N(t)$')
plt.xlim(-4,4)

#plt.hist(g, bins=30, normed=True, histtype="step", color="black", linewidth="1")
#plt.xlabel(r'g')
#plt.ylabel(r'$N(g)$')

x_axis=np.linspace(-20, 20, 1000000)
plt.plot(x_axis, stats.t.pdf(x_axis, N_measurements-1), color="orange", linewidth="1", label ="t-distribution")
plt.plot(x_axis, stats.norm.pdf(x_axis, loc=0, scale=1), color="red", linewidth="1", label = r"$\mathcal{N} \ (0,1)$")
plt.legend(loc="best")
leg = plt.legend()
leg.get_frame().set_edgecolor('black')
#plt.text(-3.5, 0.38, r"$N_{events}=$" + str(N_measurements))

mean_t= np.mean(t)
var_t= np.var(t)
skew_t = skew(t)
kurt_t = kurtosis(t, fisher=True)

print("MC:", mean_t, var_t, skew_t, kurt_t)

mean_g= np.mean(g)
var_g= np.var(g)
skew_g = skew(g)
kurt_g = kurtosis(g, fisher=True)

print("MC:", mean_g, var_g, skew_g, kurt_g)
#print("Theory:", 0,(N_measurements-1)/(N_measurements-3), 0, 6/(N_measurements-5))

plt.show()

Projects/Probability and Monte Carlo Techniques/_5_students_t.py


'''
Created on 21 Aug 2020

@author: Christoph Minz
@license: BSD 3-Clause
'''
from __future__ import annotations
from typing import List, Any, Callable
from math import log
from fractions import Fraction
import numpy as np

@np.errstate(divide='ignore')
def HarmonicNumber(n: int, approximate: int = 100) -> float:
    '''
    Returns the floating point value of the n-th harmonic number.
    For `n` greater or equal to `approximate`, the value is approximated by
    the asymptotic expansion.
    '''
    if n < approximate:
        return sum(1. / k for k in range(n, 0, -1))
    else:
        h: float = log(n) + np.euler_gamma
        h += 0.5 / n
        h -= 0.25 / 3 / n**2
        h += 0.025 / 3 / n**4
        return h

@np.errstate(divide='ignore')
def HarmonicNumbers(n: int, approximate: int = 100) -> np.ndarray:
    '''
    Returns a vector of floating point values for all harmonic numbers from 0
    to n.
    For `n` greater than `approximate`, the value is approximated by the
    asymptotic expansion.
    '''
    H: np.ndarray = np.empty((n + 1,))
    total: float = 0.
    for k in range(min(n + 1, approximate)):
        H[k] = total
        total += 1. / (k + 1)
    if n >= approximate:
        n_range: np.ndarray = np.arange(approximate, n + 1)
        H[n_range] = np.log(n_range) + np.euler_gamma
        H[n_range] += 0.5 / n_range
        H[n_range] -= 0.25 / 3 / np.square(n_range)
        H[n_range] += 0.025 / 3 / np.square(np.square(n_range))
    return H

def HarmonicNumberFraction(n: int) -> Fraction:
    '''
    Returns the exact rational number of the n-th harmonic number.
    '''
    if n == 0:
        return Fraction(0, 1)
    else:
        return Fraction(sum(Fraction(1, k) for k in range(1, n + 1)))

def HarmonicNumberFractions(n: int) -> List[Fraction]:
    '''
    Returns a vector of exact rational numbers for all harmonic numbers from
    0 to n.
    '''
    H: List[Fraction] = [Fraction()] * (n + 1)
    total: Fraction = Fraction(0, 1)
    for k in range(n + 1):
        H[k] = total
        total += Fraction(1, k + 1)
    return H

def NewtonsMethod(f: Callable[[Any], Any],
                  fprime: Callable[[Any], Any], x0: float,
                  y: float = 0.0, xmin: float = np.NINF, xmax: float = np.PINF,
                  default: float = 0.0, precission: float = 1.0e-6,
                  maxIterations: int = 50) -> float:
    '''
    Uses Newton's method to find the x value at which the function `f` (with
    its derivative `fprime`) reaches the value y with a given `precission` and
    maximal number of iterations `maxIterations`. At each iteration, the
    result will be restricted to the interval
    [`xmin` + `precission`, `xmax` - `precission`]
    If the method does not converge, `default` is returned.
    '''
    x_n: float = x0
    for _ in range(maxIterations):
        yprime_n: float = fprime(x_n)
        if yprime_n == 0.0:
            break
        x_next: float = x_n - (f(x_n) - y) / yprime_n
        if x_next < xmin:
            x_next = xmin + precission
        elif x_next > xmax:
            x_next = xmax - precission
        if abs(x_n - x_next) < precission:
            return x_next
        x_n = x_next
    return default


#symbolik:herhangi bir denklemi çözsün sonucunu x ler vs şeklinde sembolik bir şekilde işlem yapsın istersem Sympy kullanıyorum
#belirsiz sembolik matematiksel işlemleri yapmak için kullanıyorum
#yani özet olarak manuel yaptığımız x lerden y lerden oluşam matematik işlemlerimizi bu kütüphaneyle yapabiliriz
#denklem(dif denklemde dahil) çözdürüp grafik oluşturmak için Sympy kullanmak hoş olur.Sympy=SEMBOL
import sympy as sp

#sembol tanımlaması
x = sp.Symbol('xxx') #burada x bir symbol sınıfı türünden
print(x)
y = sp.Symbol('y')
z = sp.Symbol('z')
print(y)
print(z)

#3 sembolü aynı anda da yaratabilirim =>>>>>   x, y, z = sp.symbols('x, y, z')

#sembollerimi reel ve pozitif yapmak istersem
x = sp.Symbol('x', real=True, positive=True) #burada x sembolümü oluşturdum
print(x)

#integer ve float nesneler tanımlama
x = sp.Symbol('x', real=True, positive=True)
a = sp.Integer(3)
b = sp.Float('4.3476587364587364876345876867345')
print(x)
print(a)
print(b)

#x sembolüyle denklem tanımı yapma
x = sp.Symbol('x', real=True, positive=True)
y = x ** 2 - 2*x - 4
print(y)

# ifadeyi  sembolük sadeleştirmek istersem
x = sp.Symbol('x')
e = (x ** 2 - 1) / (x + 1)
result = sp.simplify(e)
print(result)

#sin2x sembolik olarak açılımını verip sonucu aldım
x = sp.Symbol('x')
e = 2 * sp.sin(x) * sp.cos(x)
result = sp.simplify(e)
print(result)

#iki değişkenden oluşan bir denklemi sadeleştirmek istersem
x = sp.Symbol('x')
y = sp.Symbol('y')
e = ((x ** 2 + x - 6) / (x ** 2 + 3 * x - 10)) * ((x ** 2 - x * y + 5 * x  - 5 * y) / (x **2 + x * y + 3 * x + 3 * y))
result = sp.simplify(e)
print(result)

#expand methoduyla denklem çarpması işlemi yaptım küp açılımı oldu
x = sp.Symbol('x')
y = sp.Symbol('y')
e = (x + y) ** 3
result = sp.expand(e)
print(result)

#çarpanlara ayırmak istersem factor methodunu kullanıyorum
x = sp.Symbol('x')
y = sp.Symbol('y')
e = x ** 2 - 2 * x + 1
result = sp.factor(e)
print(result)


import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import multivariate_normal
from mpl_toolkits.mplot3d import Axes3D

"""
Draw 1D, 2D Normal Distribution to explain my modification in Fuzzy Curve
"""
def Gaussian_Distribution(N=2, M=1000, m=0, sigma=1):
    '''
    Function
        Generate N dimensions Normal distribution data
    ----------
    Parameters
    ----------
        N 维度
        M 样本数
        m 样本均值
        sigma: 样本方差
    Returns
    -------
        data  shape(M, N), M 个 N 维服从高斯分布的样本
        Gaussian  高斯分布概率密度函数
    '''
    mean = np.zeros(N) + m  # 均值矩阵，每个维度的均值都为 m
    cov = np.eye(N) * sigma  # 协方差矩阵，每个维度的方差都为 sigma

    # 产生 N 维高斯分布数据
    data = np.random.multivariate_normal(mean, cov, M)
    # N 维数据高斯分布概率密度函数
    Gaussian = multivariate_normal(mean=mean, cov=cov)
    return data, Gaussian

def generate_1D_data():
    #-------------------- 生成两个正态分布的数据 --------------------
    # 生成mean=0.3， sigma=0.1的数据，x=【0~1，step=0.001】
    mean0, sigma0 = 0.3, 0.1
    mean1, sigma1 = 0.7, 0.2
    x = np.linspace(0, 1, 1000)

    data0, Gaussian0 = Gaussian_Distribution(N=1, M=1000, m=mean0, sigma=sigma0)
    y1 = Gaussian0.pdf(x)
    data1, Gaussian1 = Gaussian_Distribution(N=1, M=1000, m=mean1, sigma=sigma1)
    y2 = Gaussian1.pdf(x)

    with open('1D_data.txt', 'a') as f:
        line = ''
        for a,b,c in zip(x, y1, y2):
            line += ','.join([str(s) for s in [a,b,c]]) + '\n'
        f.write(line)
    #-------------------- 生成两个正态分布的数据 --------------------
# generate_1D_data()

def generate_2D_data1():
    x, y = np.mgrid[-1:1:.01, -1:1:.01]
    pos = np.empty(x.shape + (2,))  # 从x.shape=(200,200)变为(200,200,2)
    pos[:, :, 0] = x
    pos[:, :, 1] = y
    # mean=[0.5, -0.2],cov=[[2.0, 0.3], [0.3, 0.5]]，声明一个带着指定mean和cov的rv对象
    rv0 = multivariate_normal([0.5, -0.2], [[2.0, 0.3], [0.3, 0.5]])
    z0 = rv0.pdf(pos)

    rv1 = multivariate_normal([0.3, -0.7], [[2.0, 0.3], [0.3, 0.5]])
    z1 = rv1.pdf(pos)

    with open('2D_data.txt', 'a') as f:
        line = ''
        for i in range(100):
            for j in range(100):
                a, b, c, d = x[i, j], y[i, j], z0[i, j], z1[i, j]
                line += ','.join([str(s) for s in [a, b, c, d]]) + '\n'
        f.write(line)
generate_2D_data1()

def norm1D_plot(mean=0, sigma=0.1):
    data, Gaussian = Gaussian_Distribution(N=1, M=1000, m=mean, sigma=sigma)
    x = np.linspace(-1, 1, 1000)
    # 计算一维高斯概率
    y = Gaussian.pdf(x)
    plt.plot(x, y)
    plt.show()
# norm1D_plot()

def norm2D_plot(mean=0, sigma=0.1):
    M = 1000
    data, Gaussian = Gaussian_Distribution(N=2, M=mean, sigma=sigma)
    # 生成二维网格平面
    X, Y = np.meshgrid(np.linspace(-1, 1, M), np.linspace(-1, 1, M))
    # 二维坐标数据
    d = np.dstack([X, Y])
    # 计算二维联合高斯概率
    Z = Gaussian.pdf(d).reshape(M, M)

    '''二元高斯概率分布图'''
    fig = plt.figure(figsize=(12, 8))
    ax = Axes3D(fig)
    ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='seismic', alpha=0.8)
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    plt.show()
# norm2D_plot(mean=0, sigma=0.1)


import numpy as np
import math

class StaticQuantizer():
    def __init__(self, width=4, full_scale=1.0):
        self.lsb = full_scale/(2**(width-1))
        self.width = width

    def quantize(self, inp_sig):
        quantized_signal = np.array([round(x/self.lsb) for x in inp_sig])
        return np.clip(quantized_signal, -2**(self.width-1), 2**(self.width-1))

    def __call__(self, inp_sig):
        return self.quantize(inp_sig)

# Assuming Binary Fixed Point Representation
class Quantizer():
    def __init__(self, width=4, full_scale=1.0, signed=False, lsb=None, **kwargs):
        self.signed = signed
        self.full_scale = full_scale
        self.width = width
        self.lsb = lsb

    # Performs quantization of the given fixed point size given in the constructor
    def quantize(self, ideal_signal):

        lsb = self.full_scale/(2**self.width)
        quantized_signal = np.array([round(x / lsb)*lsb for x in ideal_signal])

        if self.signed:
            return np.clip(quantized_signal, -self.full_scale, self.full_scale)
        else:
            return np.clip(quantized_signal, 0, self.full_scale)

    def quantize_int(self, ideal_signal):

        lsb = self.full_scale/(2**self.width)
        quantized_signal = np.array([round(x / lsb) for x in ideal_signal])

        if self.signed:
            return np.clip(quantized_signal, -self.full_scale*2**self.width, self.full_scale)
        else:
            return np.clip(quantized_signal, 0, self.full_scale)

    def quantize_2s_comp(self, ideal_signal):
        # Assume width is the size of N-bits 2's complement. E.g. width == 2 --> [-4, 3]

        # self.signed must be true
        assert(self.signed)

        # Set the maximum magnitude ideal signal value to correspond to -127 or
        # 128 depending on if it is signed.
        if self.lsb is None:
            max_idx = np.argmax(np.absolute(ideal_signal))
            if ideal_signal[max_idx] < 0:
                lsb = -ideal_signal[max_idx] / 2**(self.width-1)
            else:
                lsb = ideal_signal[max_idx] / (2**(self.width-1) - 1)
            self.lsb = lsb

        if isinstance(ideal_signal, (list, np.ndarray)):
            quantized_signal = np.array([round(x / self.lsb) for x in ideal_signal])
            return np.clip(quantized_signal, -1 * 2**(self.width-1), 2**(self.width-1)-1)
        else:
            quantized_signal = round(ideal_signal / self.lsb)
            return max(min(quantized_signal, 2**(self.width-1)-1), -1 * 2**(self.width-1))

# Used for testing
if __name__ == "__main__":
    q = Quantizer()
    l = 10 * np.random.random_sample(10) + -10*np.random.random_sample(10)
    print(l)
    print(q.quantize(l))
    print(q.quantize_int(l))


from sys import argv

import matplotlib.pyplot as plt
import numpy as np

from utils import *
from polynomial import *

import pylab as py

def plot(dots: list[float], polynomials: list[Polynomial]) -> None:
    x, y = [p[0] for p in dots], [p[1] for p in dots]
    a, b = min(x), max(x)
    x_arg = np.linspace(a, b)

    plt.clf()

    for i in range(len(polynomials)):
        plt.subplot(1, len(polynomials), i + 1)
        plt.title(type(polynomials[i]).__name__)
        plt.xlabel("x")
        plt.ylabel("y")
        plt.grid(which='minor', color='k', linestyle=':')
        plt.grid(which='major', color='k')
        plt.minorticks_on()

        plt.plot(x, y, "xk")

        plt.plot(x_arg, [polynomials[i](arg) for arg in x_arg])

    plt.show()

if __name__ == "__main__":
    dots = read_dots(argv[1])

    print("Loaded table:")

    print_dots(dots)

    print("Enter polynomial degree and X value:")
    deg, x = read_func_data()

    newton = NewtonPolynomial.build(dots, x, deg)

    print("Newton value in {: <5.6g} is {: <5.6g}".format(x, newton(x)))

    hermite = HermitePolynomial.build(dots, x, deg)

    print("Hermite value in {: <5.6g} is {: <5.6g}".format(x, hermite(x)))

    inv_dots = [[p[1], p[0]] for p in dots]
    root = NewtonPolynomial.build(inv_dots, 0.0, deg)(0.0)

    print(
        "Root found by inverted interpolation method is {: <5.6g}".format(root))

    plot(dots, [newton, hermite])


# this package is curve-fitting using Chebyshev polynomial 1st kind and then generating polynomial coefficient
# The methodology is developed by Prof. Kwang Soo Cho
# The source code is developed by Gun Woo Park

from numpy import *
from scipy import linalg

def factorial(N):
    re = 1
    for i in range(1,N+1):
        re *= i
    return re

def Chebyshev_1st(n, x):
    if n==0:
        return 1.0
    elif n==1:
        return x
    return 2.0*x*Chebyshev_1st(n-1, x) - Chebyshev_1st(n-2, x)

def coe_Chebyshev_1st(k, n):
    if k<0 or k > n or (k==0 and n==1):
        return 0
    elif (k==1 and n==1) or (k==0 and n==0):
        return 1
    else:
        return 2*coe_Chebyshev_1st(k-1, n-1) - coe_Chebyshev_1st(k, n-2)

def cal_Cheby_1st(cn, x):
    N_n = size(cn)
    N_x = size(x)
    y = zeros(N_x)
    for i in range(N_x):
        for k in range(N_k):
            y[i] += Chebyshev_1st(k, x)*x
    return 0

# def L_nk(N, t):
#     L = zeros([N, N])
#     for i in range(N):
#         for j in range(N):
#             for k in range(size(t)):
#                 L[i,j] += Chebyshev_1st(i, t[k])*Chebyshev_1st(j, t[k])
#     return L_nk

def get_Cheby_1st_coe(t, y, N):
    a = zeros(N) # coefficient
    L = zeros([N, N]) #La = yT
    for i in range(N):
        for j in range(N):
            for k in range(size(t)):
                L[i,j] += Chebyshev_1st(i, t[k])*Chebyshev_1st(j, t[k])

    yT = zeros(N)
    for i in range(N):
        for j in range(size(y)):
            yT[i] += y[j]*Chebyshev_1st(i, t[j])
    a = linalg.inv(L).dot(yT)
    return a

def get_polynomial_from_Cheby(x, y, N):
    # note that dx must be constant for this method
    x_min = min(x)
    x_max = max(x)
    x_c = (max(x) - min(x))/2.
    dx = x[1] - x[0]
    t = 2.*(x - x_c)/dx
    a = get_Cheby_1st_coe(t, y, N)

    b = zeros(N)

    for n in range(N):
        for k in range(n, N):

            b[n] += coe_Chebyshev_1st(k, n)*a[k]
    c = zeros(N)
    for n in range(N):
        for k in range(n, N):
            c[n] += ((2./dx)**k)*(factorial(k)/(factorial(n)*factorial(k-n)))*((-x_c)**(k-n))*b[k]
    return c

def gen_from_polynomial(x, c):
    N_x = size(x)
    y = zeros(N_x)
    for i in range(N_x):
        for k in range(size(c)):
            y[i] += c[k]*x[i]**k
    return y

def diff_gen_from_polynomial(x, c):
    N_x = size(x)
    y = zeros(N_x)
    for i in range(N_x):
        for k in range(size(c)-1):
            y[i] += c[k+1]*float(k+1)*x[i]**(k)
    return y


'''
Define Bernstein Polynomials

i and p index the BPs
t is the time variable
h is the size of the timestep
'''

import scipy.special
import math

def BP(t: float, i: int, p: int, h: float):
	if i < 1 or i > p:
		return 0
	elif t >= 0 and t <= h:
		norm_coef = scipy.special.binom(p-1,i-1)
		t = t/h
		return norm_coef * t**(i-1) *  (1-t)**(p-i)
	else:
		return 0

def dBP(t: float, i: int, p: int, h: float):
	if t >= 0 and t <= h:
		return (p-1)*(BP(t, i-1, p-1, h) - BP(t, i, p -1, h) ) / h
	else:
		return 0


import numpy

def gaussian_kernel(x1, x2, sigma):
    """
    Computes the radial basis function
    Returns a radial basis function kernel between x1 and x2.

    Parameters
    ----------
    x1 :  numpy ndarray
        A vector of size (n, ), representing the first datapoint.

    x2 : numpy ndarray
        A vector of size (n, ), representing the second datapoint.

    sigma : float
        The standard deviation (bandwidth) parameter for the Gaussian kernel.

    Returns
    -------
    rbf : float
        The computed RBF between the two provided data points.

    Instructions
    ------------
    Fill in this function to return the similarity between `x1` and `x2`
    computed using a Gaussian kernel with bandwidth `sigma`.
    """
    rbf = 0
    numr = -numpy.sum((x1 - x2)**2)
    denr = 2*(sigma**2)
    rbf = numpy.exp(numr/denr)

    return rbf


import numpy as np
class Perceptron:

    def __init__(self):
        self.inputs = np.array([[1,1,1,1], [-1,1,-1,-1], [1, 1, 1, -1], [1, -1, -1, 1]])
        self.targets = np.array([1,1,-1,-1])
        self.learning_rate = 1
        self.weights = np.array([0,0,0,0])
        self.bias_weight = 0
        super().__init__()

    def train(self, epochs):
        for o in range (epochs):
            for aux,i in enumerate(self.inputs):
                ans = self.predict(i)
                if ans != self.targets[aux]:
                    for idx,val in enumerate(self.weights):
                        self.weights[idx] += (self.targets[aux] - ans) * self.inputs[aux][idx] * self.learning_rate
                        self.bias_weight += (self.targets[aux] - ans) * self.learning_rate
        return self.weights

    def predict(self,inputs):
        sum = 0
        for indx,i in enumerate(inputs):
            sum += i * self.weights[indx]
        sum += self.bias_weight
        return self.step(sum)

    def step(self, x):
        return 1 if x >= 0 else -1

P = Perceptron()
epochs = 10
weights = P.train(epochs)
inputs_t = np.array([[1,1,1,1], [-1,1,-1,-1], [1, 1, 1, -1], [1, -1, -1, 1]])
print("\nEntradas:")
print(inputs_t)
print("\nSaidas:")
for e in inputs_t:
    res = P.predict(e)
    print(res)
print("\nPesos obtidos no treinamento com " + str(epochs) + " epocas\n")
print(weights)


######################### Test Dataset #########################

# generate gaussian data samples
from numpy.random import seed
from numpy.random import randn
from numpy import mean
from numpy import std
# seed the random number generator
seed(1)
# generate two sets of univariate observations
data1 = 5 * randn(100) + 50
data2 = 5 * randn(100) + 51
# summarize
print('data1: mean=%.3f stdv=%.3f' % (mean(data1), std(data1)))
print('data2: mean=%.3f stdv=%.3f' % (mean(data2), std(data2)))

######################### Mann-Whitney U Test #########################

# Mann-Whitney U test
from numpy.random import seed
from numpy.random import randn
from scipy.stats import mannwhitneyu
# seed the random number generator
seed(1)
# generate two independent samples
data1 = 5 * randn(100) + 50
data2 = 5 * randn(100) + 51
# compare samples
stat, p = mannwhitneyu(data1, data2)
print('Statistics=%.3f, p=%.3f' % (stat, p))
# interpret
alpha = 0.05
if p > alpha:
	print('Same distribution (fail to reject H0)')
else:
	print('Different distribution (reject H0)')

######################### Wilcoxon Signed-Rank Test #########################

# Wilcoxon signed-rank test
from numpy.random import seed
from numpy.random import randn
from scipy.stats import wilcoxon
# seed the random number generator
seed(1)
# generate two independent samples
data1 = 5 * randn(100) + 50
data2 = 5 * randn(100) + 51
# compare samples
stat, p = wilcoxon(data1, data2)
print('Statistics=%.3f, p=%.3f' % (stat, p))
# interpret
alpha = 0.05
if p > alpha:
	print('Same distribution (fail to reject H0)')
else:
	print('Different distribution (reject H0)')

######################### Kruskal-Wallis H Test #########################

# generate three independent samples
data1 = 5 * randn(100) + 50
data2 = 5 * randn(100) + 50
data3 = 5 * randn(100) + 52

# Kruskal-Wallis H-test
from numpy.random import seed
from numpy.random import randn
from scipy.stats import kruskal
# seed the random number generator
seed(1)
# generate three independent samples
data1 = 5 * randn(100) + 50
data2 = 5 * randn(100) + 50
data3 = 5 * randn(100) + 52
# compare samples
stat, p = kruskal(data1, data2, data3)
print('Statistics=%.3f, p=%.3f' % (stat, p))
# interpret
alpha = 0.05
if p > alpha:
	print('Same distributions (fail to reject H0)')
else:
	print('Different distributions (reject H0)')

#########################  Friedman Test #########################

# Friedman test
from numpy.random import seed
from numpy.random import randn
from scipy.stats import friedmanchisquare
# seed the random number generator
seed(1)
# generate three independent samples
data1 = 5 * randn(100) + 50
data2 = 5 * randn(100) + 50
data3 = 5 * randn(100) + 52
# compare samples
stat, p = friedmanchisquare(data1, data2, data3)
print('Statistics=%.3f, p=%.3f' % (stat, p))
# interpret
alpha = 0.05
if p > alpha:
	print('Same distributions (fail to reject H0)')
else:
	print('Different distributions (reject H0)')


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Dec 28 10:48:04 2020

@author: jane_hsieh
"""
import numpy as np
import pandas as pd
#============================================== 0. Input ==============================================
# Observation O_t
Obs = pd.read_csv('Sample_Obs.csv', index_col = 0).values.reshape(-1,).tolist()
Obs = Obs[:100]

#Parameters
Obs_state = {str(i+1): i for i in range(6)} #name: index
Hid_state = {'F': 0, 'L': 1}                #name: index
pF = [1/6 for i in range(6)]
pL=[0.1, 0.1, 0.1, 0.1, 0.1, 0.5]

## HMM-related parameters
Pi = np.array([0.5, 0.5])   # initial probs of hidden states {0, 1}  [0: fair dice; 1: Loaded dice]; p(S_0=1) = 1-pi_0
P = np.array([[0.95, 0.05], [0.05, 0.95]]) #Transition matrix T = [p_{i,j}] = [p(Sj|Si)]
E = np.array([pF,pL]) #Emission matrix E = [q_i^y] = [p(O=y|S=i)]

#============================================== 1. Alpha ==============================================

def Build_Alpha(Obs, Pi, P, E):
    '''
    Use Forward Algorithm to compute Alpha (dim=TxK)
    Alpha = [alpha_1, ..., alpha_T]^T, alpha_t = [alpha_t^0, ..., alpha_t^{K-1}]^T
    Issue: if T too large, Alpha_t --> 0 as t--> T
    '''
    T = len(Obs)
    K = len(Pi)
    Alpha = np.zeros([T,K])

    #Initial:
    index_E = Obs_state[str(Obs[0])]
    Alpha[0] = Pi * E[:, index_E]

    #Iterate:
    for t in range(1,T):
        index_E = Obs_state[str(Obs[t])]
        Alpha[t] = np.dot(Alpha[t-1], P) * E[:, index_E]

    return Alpha

Alpha = Build_Alpha(Obs, Pi, P, E)

Evaluation = Alpha[-1].sum()

#============================================== 2. Beta ==============================================
def Build_Beta(Obs, Pi, P, E):
    '''
    Use Backward Algorithm to compute Beta (dim=TxK)
    Beta = [beta_1, ..., beta_T]^T, beta_t = [beta_t^0, ..., beta_t^{K-1}]^T
    '''
    T = len(Obs)
    K = len(Pi)
    Beta = np.zeros([T,K])

    #Initial:
    Beta[T-1] = np.ones(K)

    #Iterate:
    for t in range(T-2,-1,-1):
        index_E = Obs_state[str(Obs[t+1])]
        Beta[t] = np.dot(Beta[t+1] * E[:, index_E], P.T)

    return Beta

Beta = Build_Beta(Obs, Pi, P, E)

#Termination:
# Decoding1 matrix is p(S_t=k|{O_t}_{t=1}^T)
Decoding1 = Alpha * Beta
Decoding1 = Decoding1 / Decoding1.sum(axis =1)[:, np.newaxis]

Seq_Decoding1 = Decoding1.argmax(axis = 1)

#============================================== 3. Viterbi & Traceback ==================================================
def Build_Viterbi(Obs, Pi, P, E):
    '''
    Use Viterbi Algorithm to compute V (dim=TxK)
    V = [V_1, ..., V_T]^T, V_t = [V_t^0, ..., V_t^{K-1}]^T
    '''
    T = len(Obs)
    K = len(Pi)
    V = np.zeros([T,K])

    #Initial:
    index_E = Obs_state[str(Obs[0])]
    V[0] = Pi * E[:, index_E]

    #Iterate:
    for t in range(1,T):
        index_E = Obs_state[str(Obs[t])]
        V[t] =   (V[t-1].reshape(-1,1) * P).max(axis=0)  * E[:, index_E]

    return V

V = Build_Viterbi(Obs, Pi, P, E)

def Viterbi_Traceback(V):
    '''
    find most likely assignment of state sequence (i.e., Seq_Decoding2)
    '''
    T, K = V.shape
    Seq_Decoding2 = np.zeros(T)

    #Termination:
    Seq_Decoding2[T-1] = V[T-1].argmax()

    #Iterate:
    for t in range(T-1,0,-1):
        index_P = int(Seq_Decoding2[t] )
        Seq_Decoding2[t-1] = (V[t-1] * P[:, index_P]).argmax()

    return Seq_Decoding2

Seq_Decoding2 = Viterbi_Traceback(V)

Hidden Markov Model (HMM)/Dice Simulation Data/Step2. HMM Algorithms Breakdown/Step2. HMM Algorithms Breakdown.py


"""

https://apmonitor.com/pdc/index.php/Main/ModelSimulation
http://web.mit.edu/2.14/www/Handouts/StateSpaceResponse.pdf

Integrate the differential equation in various ways,
but give the approximately the same solution numerically

"""

from math import *
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from scipy.integrate import odeint
from scipy.linalg import expm
from sympy.abc import s, t
from sympy.integrals import inverse_laplace_transform

# Simulate taup * dy/dt = -y + K*u
Kp = 10
taup = 3

# (1) Transfer Function
num = [Kp]
den = [taup,1]
sys1 = signal.TransferFunction(num,den)
t1,y1 = signal.step(sys1)

# (2) State Space
A = -1.0/taup
B = Kp/taup
C = 1.0
D = 0.0
sys2 = signal.StateSpace(A,B,C,D)
t2,y2 = signal.step(sys2)

# (3) ODE Integrator
def model3(y, t):
    u = 1.0
    return (-y + Kp * u)/taup
t3 = np.linspace(0,14,100)
y3 = odeint(model3,0,t3)

# (4) Closed Form
# y' = -ay + b
# a = -1/taup
# b = Kp*u/taup
# k1 is the arbitrary constant based on initial condition
# y(0) = 0
# y(t) = b/a + k1*exp(-a*t)
def closed(t):
    u = 1.0
    a = 1.0/taup
    b = Kp*u/taup
    k1 = -b/a
    return b/a + k1*exp(-a*t)
y4 = np.array([closed(t) for t in t3])

# (5) Symbolic
# We multiply the transfer function G(s) with the unit step response U(s)
# and then take the inverse laplace transform to find the equation
# for evaluation
G = Kp/(taup*s + 1)
U = 1/s
Y = inverse_laplace_transform(G*U, s, t)
y5 = np.array([Y.subs(t, ts) for ts in t3])

# (6) Manual State Space
# This should be the same as (2) but we do it manually rather than using a library
# x(0) = 0 with unit step function gives rise to the following update equation
# x(t) = A**-1*(expm(A*t) - I)*B
# y(t) = C*x(t) + D*step(t)
def stspsim(t):
    MA = np.array([[A]])
    MB = np.array([[B]])
    MC = np.array([[C]])
    MD = np.array([[D]])
    I = np.array([[1.0]])

    x = MA**-1 @ (expm(MA*t) - I) @ MB
    y = MC*x + MD*np.heaviside(t, 1)
    return y
y6 = np.array([stspsim(t) for t in t3])
y6 = y6.flatten()

print("Transfer Function: {}".format(Y))

plt.figure(1)
plt.plot(t1,y1,'b--',linewidth=6,label='Transfer Function')
plt.plot(t2,y2,'g:',linewidth=5,label='State Space')
plt.plot(t3,y3,'r-',linewidth=4,label='ODE Integrator')
plt.plot(t3,y4,'p-',linewidth=3,label='Closed Form')
plt.plot(t3,y5,'c-',linewidth=2,label='Symbolic')
plt.plot(t3,y6,'m*',linewidth=1,label='State Space Manual')
plt.xlabel('Time')
plt.ylabel('Response (y)')
plt.legend(loc='best')
plt.savefig('1st_order.png')
plt.show()


'''
Applying an MA Model

The bouncing of the stock price between bid and ask induces a negative first order autocorrelation, but no autocorrelations at lags higher than 1. You get the same ACF pattern with an MA(1) model. Therefore, you will fit an MA(1) model to the intraday stock data from the last exercise.

The first step is to compute minute-by-minute returns from the prices in intraday, and plot the autocorrelation function. You should observe that the ACF looks like that for an MA(1) process. Then, fit the data to an MA(1), the same way you did for simulated data.

INSTRUCTIONS
100XP
Import plot_acf and ARMA modules from statsmodels
Compute minute-to-minute returns from prices:
Compute returns with the .pct_change() method
Use the pandas method .dropna() to drop the first row of returns, which is NaN
Plot the ACF function with lags up to 60 minutes
Fit the returns data to an MA(1) model and print out the MA(1) parameter
'''
# Import plot_acf and ARMA modules from statsmodels
from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.tsa.arima_model import ARMA

# Compute returns from prices and drop the NaN
returns = intraday.pct_change()
returns = returns.dropna()

# Plot ACF of returns with lags up to 60 minutes
plot_acf(returns, lags=60)
plt.show()

# Fit the data to an MA(1) model
mod = ARMA(returns, order=(0,1))
res = mod.fit()
print(res.params)


import math
import random

import cv2
import numpy as np

eTranslate = 0
eHomography = 1

def computeHomography(f1, f2, matches, A_out=None):
    '''
    Input:
        f1 -- list of cv2.KeyPoint objects in the first image
        f2 -- list of cv2.KeyPoint objects in the second image
        matches -- list of cv2.DMatch objects
            DMatch.queryIdx: The index of the feature in the first image
            DMatch.trainIdx: The index of the feature in the second image
            DMatch.distance: The distance between the two features
        A_out -- ignore this parameter. If computeHomography is needed
                 in other TODOs, call computeHomography(f1,f2,matches)
    Output:
        H -- 2D homography (3x3 matrix)
        Takes two lists of features, f1 and f2, and a list of feature
        matches, and estimates a homography from image 1 to image 2 from the matches.
    '''
    num_matches = len(matches)

    # Dimensions of the A matrix in the homogenous linear
    # equation Ah = 0
    num_rows = 2 * num_matches
    num_cols = 9
    A_matrix_shape = (num_rows,num_cols)
    A = np.zeros(A_matrix_shape)

    for i in range(len(matches)):
        m = matches[i]
        (a_x, a_y) = f1[m.queryIdx].pt
        (b_x, b_y) = f2[m.trainIdx].pt

        #BEGIN TODO 2
        #Fill in the matrix A in this loop.
        #Access elements using square brackets. e.g. A[0,0]
        #TODO-BLOCK-BEGIN
        A[2*i, 0] = a_x
        A[2*i, 1] = a_y
        A[2*i, 2] = 1
        A[2*i, 3] = 0
        A[2*i, 4] = 0
        A[2*i, 5] = 0
        A[2*i, 6] = -b_x * a_x
        A[2*i, 7] = -b_x * a_y
        A[2*i, 8] = -b_x

        A[2*i+1,0] = 0
        A[2*i+1,1] = 0
        A[2*i+1,2] = 0
        A[2*i+1,3] = a_x
        A[2*i+1,4] = a_y
        A[2*i+1,5] = 1
        A[2*i+1,6] = -b_y * a_x
        A[2*i+1,7] = -b_y * a_y
        A[2*i+1,8] = -b_y

        #TODO-BLOCK-END
        #END TODO

    U, s, Vt = np.linalg.svd(A)

    if A_out is not None:
        A_out[:] = A

    #s is a 1-D array of singular values sorted in descending order
    #U, Vt are unitary matrices
    #Rows of Vt are the eigenvectors of A^TA.
    #Columns of U are the eigenvectors of AA^T.

    #Homography to be calculated
    H = np.eye(3)

    #BEGIN TODO 3
    #Fill the homography H with the appropriate elements of the SVD
    #TODO-BLOCK-BEGIN
    minsv = Vt.shape[0] - 1
    H = (Vt[minsv] / Vt[minsv][8]).reshape(3,3)
    #TODO-BLOCK-END
    #END TODO

    return H

def alignPair(f1, f2, matches, m, nRANSAC, RANSACthresh):
    '''
    Input:
        f1 -- list of cv2.KeyPoint objects in the first image
        f2 -- list of cv2.KeyPoint objects in the second image
        matches -- list of cv2.DMatch objects
            DMatch.queryIdx: The index of the feature in the first image
            DMatch.trainIdx: The index of the feature in the second image
            DMatch.distance: The distance between the two features
        m -- MotionModel (eTranslate, eHomography)
        nRANSAC -- number of RANSAC iterations
        RANSACthresh -- RANSAC distance threshold

    Output:
        M -- inter-image transformation matrix
        Repeat for nRANSAC iterations:
            Choose a minimal set of feature matches.
            Estimate the transformation implied by these matches
            count the number of inliers.
        For the transformation with the maximum number of inliers,
        compute the least squares motion estimate using the inliers,
        and return as a transformation matrix M.
    '''

    #BEGIN TODO 4
    #Write this entire method.  You need to handle two types of
    #motion models, pure translations (m == eTranslation) and
    #full homographies (m == eHomography).  However, you should
    #only have one outer loop to perform the RANSAC code, as
    #the use of RANSAC is almost identical for both cases.

    #Your homography handling code should call compute_homography.
    #This function should also call get_inliers and, at the end,
    #least_squares_fit.
    #TODO-BLOCK-BEGIN
    Sample_size = 1 if m == eTranslate else 4
    max_inlier = 0
    count_inlier = []

    for i in range(nRANSAC):
        H = np.eye(3)
        randMatch = np.random.choice(matches, Sample_size)

        if m == eTranslate:
            match = randMatch[0]
            H[0][0] = 1
            H[0][1] = 0
            H[0][2] = f2[match.trainIdx].pt[0] - f1[match.queryIdx].pt[0]
            H[1][0] = 0
            H[1][1] = 1
            H[1][2] = f2[match.trainIdx].pt[1] - f1[match.queryIdx].pt[1]
            H[2][0] = 0
            H[2][1] = 0
            H[2][2] = 1
        elif m == eHomography:
            H = computeHomography(f1,f2,randMatch)

        num_inliers = getInliers(f1, f2, matches, H, RANSACthresh)
        if len(num_inliers) > max_inlier:
            count_inlier = num_inliers
            max_inlier = len(num_inliers)

        #call least_squares_fit
        M = leastSquaresFit(f1, f2, matches, m, count_inlier)
    #TODO-BLOCK-END
    #END TODO
    return M

def getInliers(f1, f2, matches, M, RANSACthresh):
    '''
    Input:
        f1 -- list of cv2.KeyPoint objects in the first image
        f2 -- list of cv2.KeyPoint objects in the second image
        matches -- list of cv2.DMatch objects
            DMatch.queryIdx: The index of the feature in the first image
            DMatch.trainIdx: The index of the feature in the second image
            DMatch.distance: The distance between the two features
        M -- inter-image transformation matrix
        RANSACthresh -- RANSAC distance threshold

    Output:
        inlier_indices -- inlier match indices (indexes into 'matches')

        Transform the matched features in f1 by M.
        Store the match index of features in f1 for which the transformed
        feature is within Euclidean distance RANSACthresh of its match
        in f2.
        Return the array of the match indices of these features.
    '''

    inlier_indices = []

    for i in range(len(matches)):
        #BEGIN TODO 5
        #Determine if the ith matched feature f1[id1], when transformed
        #by M, is within RANSACthresh of its match in f2.
        #If so, append i to inliers
        #TODO-BLOCK-BEGIN
        # features
        m = matches[i]
        (a_x, a_y) = f1[m.queryIdx].pt
        (b_x, b_y) = f2[m.trainIdx].pt

        # Transform
        p = np.zeros(3)
        p[0] = a_x
        p[1] = a_y
        p[2] = 1
        translated = np.dot(M , p)
        xt = translated[0] / translated[2]
        yt = translated[1] / translated[2]

        # Euclidean distance
        x_diff = (b_x - xt) * (b_x - xt)
        y_diff = (b_y - yt) * (b_y - yt)
        distance = np.sqrt(x_diff + y_diff)
        if distance <= RANSACthresh:
        	inlier_indices.append(i)
        #TODO-BLOCK-END
        #END TODO

    return inlier_indices

def leastSquaresFit(f1, f2, matches, m, inlier_indices):
    '''
    Input:
        f1 -- list of cv2.KeyPoint objects in the first image
        f2 -- list of cv2.KeyPoint objects in the second image
        matches -- list of cv2.DMatch objects
            DMatch.queryIdx: The index of the feature in the first image
            DMatch.trainIdx: The index of the feature in the second image
            DMatch.distance: The distance between the two features
        m -- MotionModel (eTranslate, eHomography)
        inlier_indices -- inlier match indices (indexes into 'matches')

    Output:
        M - transformation matrix

        Compute the transformation matrix from f1 to f2 using only the
        inliers and return it.
    '''

    # This function needs to handle two possible motion models,
    # pure translations (eTranslate)
    # and full homographies (eHomography).

    M = np.eye(3)

    if m == eTranslate:
        #For spherically warped images, the transformation is a
        #translation and only has two degrees of freedom.
        #Therefore, we simply compute the average translation vector
        #between the feature in f1 and its match in f2 for all inliers.

        u = 0.0
        v = 0.0

        for i in range(len(inlier_indices)):
            #BEGIN TODO 6
            #Use this loop to compute the average translation vector
            #over all inliers.
            #TODO-BLOCK-BEGIN
            (a_x, a_y) = f1[matches[inlier_indices[i]].queryIdx].pt
            (b_x, b_y) = f2[matches[inlier_indices[i]].trainIdx].pt

        	#sum the difference
            u = u + b_x - a_x
            v = v + b_y - a_y
            #TODO-BLOCK-END
            #END TODO

        u /= len(inlier_indices)
        v /= len(inlier_indices)

        M[0,2] = u
        M[1,2] = v

    elif m == eHomography:
        #BEGIN TODO 7
        #Compute a homography M using all inliers.
        #This should call computeHomography.
        #TODO-BLOCK-BEGIN
        inmatches = []
        for i in range(len(inlier_indices)):
        	inmatches.append(matches[inlier_indices[i]])

        M = computeHomography(f1,f2,inmatches)
        #TODO-BLOCK-END
        #END TODO

    else:
        raise Exception("Error: Invalid motion model.")

    return M


import numpy as np
import matplotlib.pyplot as plt

# X (ndarray)
# [[ 3.38156267  3.38911268]
#  [ 4.52787538  5.8541781 ]
#  [ 2.65568187  4.41199472]]

# support functions ---------------------------------------
def plot_n_image(X, n):
    """ plot first n images
    n has to be a square number
    """
    pic_size = int(np.sqrt(X.shape[1]))
    grid_size = int(np.sqrt(n))

    first_n_images = X[:n, :]

    fig, ax_array = plt.subplots(nrows=grid_size, ncols=grid_size,
                                    sharey=True, sharex=True, figsize=(8, 8))

    for r in range(grid_size):
        for c in range(grid_size):
            ax_array[r, c].imshow(first_n_images[grid_size * r + c].reshape((pic_size, pic_size)))
            plt.xticks(np.array([]))
            plt.yticks(np.array([]))

# PCA functions ---------------------------------------
def covariance_matrix(X):
    """
    Args:
        X (ndarray) (m, n)
    Return:
        cov_mat (ndarray) (n, n):
            covariance matrix of X
    """
    m = X.shape[0]

    return (X.T @ X) / m

def normalize(X):
    """
        for each column, X-mean / std
    """
    X_copy = X.copy()
    m, n = X_copy.shape

    for col in range(n):
        X_copy[:, col] = (X_copy[:, col] - X_copy[:, col].mean()) / X_copy[:, col].std()

    return X_copy

def pca(X):
    """
    http://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.svd.html
    Args:
        X ndarray(m, n)
    Returns:
        U ndarray(n, n): principle components
    """
    # 1. normalize data
    X_norm = normalize(X)

    # 2. calculate covariance matrix
    Sigma = covariance_matrix(X_norm)  # (n, n)

    # 3. do singular value decomposition
    # remeber, we feed cov matrix in SVD, since the cov matrix is symmetry
    # left sigular vector and right singular vector is the same, which means
    # U is V, so we could use either one to do dim reduction
    U, S, V = np.linalg.svd(Sigma)  # U: principle components (n, n)

    return U, S, V

def project_data(X, U, k):
    """
    Args:
        U (ndarray) (n, n)
    Return:
        projected X (n dim) at k dim
    """
    m, n = X.shape

    if k > n:
        raise ValueError('k should be lower dimension of n')

    return X @ U[:, :k]

def recover_data(Z, U):
    m, n = Z.shape

    if n >= U.shape[0]:
        raise ValueError('Z dimension is >= U, you should recover from lower dimension to higher')

    return Z @ U[:, :n].T


import numpy as np
import time
from scipy.linalg import solve
def create_tridiag(l, r, u, n):
    A = np.zeros((n, n))
    temp = np.ones(n - 1)
    np.fill_diagonal(A, r)
    np.fill_diagonal(A[1:], l)
    np.fill_diagonal(A[:, 1:], u)
    return A

def seidel(a, x, b, Iteration= False):
    x0 = np.copy(x)
    n = len(x)
    # Finding X1
    for j in range(n):
        d = b[j]
        for i in range(0, n):
            if (j != i):
                d -= a[j][i] * x[i]
        x[j] = d / a[j][j]
    # Iterate Seidel
    while np.linalg.norm(np.subtract(x, x0)) > 0.0001:
        if Iteration == True:
            print("Iteration: " + str(x))
        x0 = np.copy(x)
        for j in range(0, n):
            # temp variable d to store b[j]
            d = b[j]
            for i in range(0, n):
                if (j != i):
                    d -= a[j][i] * x[i]
                # updating the value of our solution
            x[j] = d / a[j][j]
        # returning our updated solution
    return x

n = 50
a = create_tridiag(1, 4, -1, n)
b = np.ones(n)
x = np.ones(n)
print(x)

# loop run for m times depending on m the error value
start = time.time()
x = seidel(a, x, b, Iteration= False)
    # print each time the updated solution
end = time.time()
print("Scipy Result: ", solve(a, b))
print(x)
print("Time: ", end - start)