import sympy as sp
from sympy import cos, sin
from sympy.core.rules import Transform
from sympy import Float

class cVsdkSym(object):
    ''' Direct Kinematic symbolic function generator.  Very symple class meant
    to generate a symbolic sympy expression of the direct kinematic function of
    a robot and its jacobian.
    '''

    def __init__(self):
        self.mij_ = []
        self.m0j_ = []
        self.q_ = []
        self.dim_ = 0
        self.mee = sp.eye(4)

    def add_link(self, _a, _d, _alpha, _theta, _q=None):
        ''' Adds a new link to the robot with given DH
        parameters'''
        self.mij_.append(cDHmatrixSym(_a, _d, _alpha, _theta))
        self.m0j_.append(sp.zeros(4, 4))
        if _q is None:
            qs = sp.symbols('q[{:d}]'.format(len(self.q_)), real=True)
        else:
            qs = _q
        self.q_.append(qs)
        self.dim_ += 1

    def set_tcp_offset(self, _x, _y, _z):
        self.mee[0:3, 3] = _x, _y, _z

    def __call__(self, _q=None):

        if _q is None:
            q = self.q_
        else:
            q = _q

        self.m0j_[0] = self.mij_[0](q[0])

        for i, mij in enumerate(self.mij_[1:], start=1):
            self.m0j_[i] = self.m0j_[i - 1] * mij(q[i])

        res = self.m0j_[-1] * self.mee
        for i in range(3):
            for j in range(4):
                res[i, j] = res[i, j].xreplace(
                    Transform(lambda x: x.round(8),
                              lambda x: isinstance(x, Float)))
        return res

    def __len__(self):
        return len(self.mij_)

    def jac(self, _q=None):

        if _q is None:
            q = self.q_
        else:
            q = _q

        pe = self(q)[:3, -1]
        jac = sp.zeros(6, self.dim_)
        axis = sp.Matrix([0.0, 0.0, 1.0])
        p = sp.Matrix([0.0, 0.0, 0.0])
        for j in range(0, self.dim_):
            jac[:3, j] = axis.cross(pe - p).doit()
            jac[3:, j] = axis
            axis = self.m0j_[j][:3, 2]
            p = self.m0j_[j][:3, -1]

        for i in range(6):
            for j in range(self.dim_):
                jac[i, j] = jac[i, j].xreplace(
                    Transform(lambda x: x.round(8),
                              lambda x: isinstance(x, Float)))

        return jac

class cDHmatrixSym(object):
    ''' Symbolic DH transformation matrix generator '''

    def __init__(self, _a, _d, _alpha, _theta):
        self.buff_ = sp.zeros(4, 4)
        self.theta_ = _theta
        self.alpha_ = _alpha
        self.a_ = _a
        self.d_ = _d
        self.cosal_ = cos(_alpha)
        self.sinal_ = sin(_alpha)

    def __call__(self, _q):
        cth = cos(self.theta_ + _q)
        sth = sin(self.theta_ + _q)
        res = sp.zeros(4, 4)
        res[0, 0] = cth
        res[0, 1] = -sth * self.cosal_
        res[0, 2] = sth * self.sinal_
        res[0, 3] = self.a_ * cth

        res[1, 0] = sth
        res[1, 1] = cth * self.cosal_
        res[1, 2] = -cth * self.sinal_
        res[1, 3] = self.a_ * sth

        res[2, 1] = self.sinal_
        res[2, 2] = self.cosal_
        res[2, 3] = self.d_

        res[3, 3] = 1

        for i in range(3):
            for j in range(4):
                res[i, j] = res[i, j].xreplace(
                    Transform(lambda x: x.round(8),
                              lambda x: isinstance(x, Float)))
        return res

def dh_sym_matrix(_q, _a, _d, _alpha, _theta):
    theta_ = _theta
    alpha_ = _alpha
    a_ = _a
    d_ = _d
    cosal_ = cos(alpha_)
    sinal_ = sin(alpha_)

    cth = cos(theta_ + _q)
    sth = sin(theta_ + _q)
    res = sp.zeros(4, 4)
    res[0, 0] = cth
    res[0, 1] = -sth * cosal_
    res[0, 2] = sth * sinal_
    res[0, 3] = a_ * cth

    res[1, 0] = sth
    res[1, 1] = cth * cosal_
    res[1, 2] = -cth * sinal_
    res[1, 3] = a_ * sth

    res[2, 1] = sinal_
    res[2, 2] = cosal_
    res[2, 3] = d_

    res[3, 3] = 1
    return res


import numpy as np
from  tensorcomlib import base
from  tensorcomlib import tensor
from  sklearn.utils.extmath import randomized_svd
from  tensorcomlib.MatrixSVD import SVD
from matplotlib.pylab import plt
import time

#hosvd
def hosvd(X):
    U = [None for _ in range(X.ndims())]
    dims = X.ndims()
    S = X
    for d in range(dims):
        C = base.unfold(X,d)
        U1,S1,V1 = np.linalg.svd(C,full_matrices=False)
        S = base.tensor_times_mat(S, U1.T,d)
        U[d] = U1
    core  = S
    return U,core

#randomized_hosvd
def randomized_hosvd(X):
    U = [None for _ in range(X.ndims())]
    dims = X.ndims()
    S = X
    for d in range(dims):
        C = base.unfold(X,d)
        U1, S1, V1 = randomized_svd(C, n_components=3, n_oversamples=10, n_iter='auto',
                     power_iteration_normalizer='auto', transpose='auto',
                     flip_sign=True, random_state=42)
        S = base.tensor_times_mat(S, U1.T, d)
        U[d] = U1
    core = S
    return U, core

#TruncatedHosvd
def TruncatedHosvd(X,eps):
    U = [None for _ in range(X.ndims())]
    dims = X.ndims()
    S = X
    R = [None for _ in range(X.ndims())]
    for d in range(dims):
        C = base.unfold(X,d)
        U1,S1,r = SVD.TruncatedSvd(C,eps_svd=eps)
        R[d] = r
        U[d] = U1
        S = base.tensor_times_mat(S,U[d].T,d)
    return U,S,R,eps

#PartialHosvd
def PartialHosvd(X,ranks):
    U = [None for _ in range(X.ndims())]
    dims = X.ndims()
    S = X
    for d in range(dims):
        C = base.unfold(X, d)
        U1,_,_= SVD.PartialSvd(C,ranks[d])
        U[d] = U1
        S = base.tensor_times_mat(S, U[d].T, d)
    return U, S

#hooi
def hooi(X,maxiter=1000,init='svd',eps = 1e-11,tol=1e-10, plot = True):

    time0 = time.time()

    dims = X.ndims()
    modelist = list(range(dims))

    if init == 'svd':
        U,core,ranks,eps_svd = TruncatedHosvd(X,eps= eps)
        print('TruncatedHosvd Ranks:\t'+str(ranks))
        data = base.tensor_multi_times_mat(core, U, modelist=modelist, transpose=False)
        errorsvd = base.tennorm(base.tensor_sub(data, X))
        print('---------------------------->>>>>>')
        print('TruncatedHosvd Init:')
        print("Original tensor:",X.data.reshape(1024)[1:10])
        print("TruncatedHosvd tensor:",data.data.reshape(1024)[1:10])
        print("Truncated error:",errorsvd)

    else:
        U,core = randomized_hosvd(X)

    error_X = []
    error_iter = []

    normx = base.tennorm(X)
    S1 = X

    for iteration in range(maxiter):
        Uk = [None for _ in range(dims)]
        for i in range(dims):
            U1 = U.copy()
            U1.pop(i)
            L = list(range(dims))
            L.pop(i)
            Y = base.tensor_multi_times_mat(X,U1,modelist=L,transpose=True)
            C = base.unfold(Y,i)
            Uk[i],_,_ = SVD.PartialSvd(C,ranks[i])

        core = base.tensor_multi_times_mat(X,Uk,list(range(dims)),transpose=True)
        U = Uk
        S2 = base.tensor_multi_times_mat(core,Uk,list(range(dims)),transpose=False)
        error0 = base.tennorm(base.tensor_sub(S2,S1))
        S1 = S2
        error_iter.append(error0)
        error1 = base.tennorm(base.tensor_sub(X, S2))
        error_X.append(error1)

        if error0<tol:
            print('---------------------------->>>>>>')
            print('HOOI:')
            print('Iteration:' + str(iteration) + '\t\t' + 'Error_iter:' + str(error0)+'\t\t'+'Error_X:' + str(error1))
            print("Cost time:",time.time()-time0)
            break

    if plot:
        plt.plot(error_X)
        plt.title('The norm difference between the reduction tensor and the original tensor')
        plt.xlabel('Iteration')
        plt.ylabel('Norm difference')
        plt.show()
        plt.plot(error_iter)
        plt.title('The difference between the norm of restoring tensors in two consecutive iterations')
        plt.xlabel('Iteration')
        plt.ylabel('Norm difference')
        plt.show()

    return  U,core


# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial.distance import pdist

v1 = np.array([1.0, 2.0, 3.0])
v2 = np.array([9.0, 8.0, 7.0])

def euclidean(vec1, vec2):
    s = 0
    for i in range(len(vec1)):
        s += (vec1[i] - vec2[i]) ** 2
    return np.sqrt(s)

def euclidean2(vec1, vec2):
    return np.sqrt(np.sum(np.square(vec1 - vec2)))

def euclidean3(vec1, vec2):
    diff = vec1 - vec2
    return np.sqrt(np.dot(diff, diff.T))

def euclidean4(vec1, vec2):
    return np.linalg.norm(vec1 - vec2, ord=2)

print("euclidean = {0}".format(euclidean(v1, v2)))
print("euclidean2 = {0}".format(euclidean2(v1, v2)))
print("euclidean3 = {0}".format(euclidean3(v1, v2)))
print("euclidean4 = {0}".format(euclidean4(v1, v2)))

# 曼哈顿距离 (Manhattan Distance)：
# 又称城市街区距离，顾名思义，就像穿行在曼哈顿的街区一样，只能走直线然后拐弯在接着走直线，而不是两点之间直线最短
# \sum_{k=1}^n |x_{1k} - x_{2k}|
def manhattan(vec1, vec2):
    return np.sum(np.abs(vec1 - vec2))

def manhattan2(vec1, vec2):
    return np.linalg.norm(vec1 - vec2, ord=1)

print("manhattan = {0}".format(manhattan(v1, v2)))
print("manhattan2 = {0}".format(manhattan2(v1, v2)))

# 切比雪夫距离 (Chebyshev Distance)：
# 一张国际象棋的棋盘可以说明这个距离。大家都知道国际象棋中的后非常自由，可以用任意方法走任意多格，
# 切比雪夫距离就相当于问后从棋盘上一个位置到另一个位置最少需要走多少步（假设一步只能走一格，但这
# 一格可以是朝任何方向，直线斜线均可，用图像处理的概念说就是8邻域内的任何一个位置都可以一步走到）。
# 举个例子，从（1,1）位置走到（4,5）位置，后可以先斜向走到（2,2），然后（3,3），再然后（4,4），
# 最后直走一步到（4,5），也就是一共需要走4步，因此这两点的切比雪夫距离就是4。
# max |x_{1i} - x_{2i}|
def chebyshev(vec1, vec2):
    distance = 0
    for i in range(len(vec1)):
        m = abs(vec1[i] - vec2[i])
        if m > distance:
            distance = m
    return distance

def chebyshev2(vec1, vec2):
    return np.linalg.norm(vec1 - vec2, ord=np.inf)

print("chebyshev = {0}".format(chebyshev(v1, v2)))
print("chebyshev2 = {0}".format(chebyshev2(v1, v2)))

# 以上三种距离本质都是闵可夫斯基（Minkowski）距离的特定形式，切比雪夫距离中n取了无穷大，曼哈顿距离中n=1，而欧氏距离中n=2.
# D = \left( \sum_{i=1}^n \left|x_i - y_i\right|^p \right)^{1/p}
# \lim_{p \to \inf} D = max |x_{1i} - x_{2i}|
def minkowski(vec1, vec2, c=2.0):
    return np.sum(np.abs(vec1 - vec2) ** c) ** (1.0 / c)

print("minkowski(1) = {0}".format(minkowski(v1, v2, 1)))
print("minkowski(2) = {0}".format(minkowski(v1, v2, 2)))
print("minkowski(3) = {0}".format(minkowski(v1, v2, 3)))
print("minkowski(100) = {0}".format(minkowski(v1, v2, 100.0)))

# cos(\theta) = \frac{\sum_{k=1}^n x_{1k}x_{2k}}{\sqrt{\sum_{k=1}^n x_{1k}^2} \sqrt{\sum_{k=1}^n x_{2k}^2}}
# cos(\theta) = \frac{AB}{|A||B|}
def cosine(vec1, vec2):
    a = np.dot(vec1, vec2)
    b1 = np.sqrt(np.dot(vec1, vec1.T))
    b2 = np.sqrt(np.dot(vec2, vec2.T))
    return a / (b1 * b2)

def cosine2(vec1, vec2):
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

# print(np.hstack([v1, v2]))
# print(np.vstack([v1, v2]))
# print(np.c_[v1, v2])
# print(np.r_[v1, v2])

def cosine3(vec1, vec2):
    x = np.vstack([vec1, vec2])
    # x = np.array([vec1, vec2])
    return (1 - pdist(x, 'cosine'))[0]

print("cosine = {0}".format(cosine(v1, v2)))
print("cosine2 = {0}".format(cosine2(v1, v2)))
print("cosine3 = {0}".format(cosine3(v1, v2)))

# 杰卡德相似系数
# J(A, B) = \frac{|A \bigcap B|}{|A \bigcup B|}
# J_{\delta}(A,B) = 1 − J(A,B)= \frac{|A \bigcup B|−|A \bigcap B|}{|A \bigcup B|}

def jaccard(vec1, vec2):
    up = np.bitwise_and((vec1 != vec2), np.bitwise_or(vec1 != 0, vec2 != 0)).sum()
    down = np.bitwise_or(vec1 != 0, vec2 != 0).sum()
    return up / down

def jaccard2(vec1, vec2):
    x = np.vstack([vec1, vec2])
    return pdist(x, 'jaccard')[0]

print("jaccard = {0}".format(jaccard(v1, v2)))
print("jaccard2 = {0}".format(jaccard2(v1, v2)))

# fig = plt.figure()
# ax = fig.add_subplot(111, projection='3d')
#
# x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# y = [5, 6, 2, 3, 13, 4, 1, 2, 4, 8]
# z = [2, 3, 3, 3, 5, 7, 9, 11, 9, 10]
#
# ax.scatter(*v1, c='r', marker='o')
# ax.scatter(*v2, c='b', marker='o')
#
#
# ax.set_xlabel('X Axis')
# ax.set_ylabel('Y Axis')
# ax.set_zlabel('Z Axis')
#
# plt.show()


from plotly.subplots import make_subplots

from IMLearn.learners import UnivariateGaussian, MultivariateGaussian
import numpy as np
import plotly.graph_objects as go
import plotly.io as pio

pio.templates.default = "simple_white"

def test_univariate_gaussian():
    # Question 1 - Draw samples and print fitted model

    samples = np.random.normal(10, 1, 1000)

    ug = UnivariateGaussian()
    ug.fit(samples)
    print(ug.mu_, ug.var_)

    # Question 2 - Empirically showing sample mean is consistent

    y_axis = []
    x_axis = []

    for i in range(0, samples.size, 10):
        ug.fit(samples[:i + 10])
        y_axis.append(abs(10 - ug.mu_))
        x_axis.append(i + 10)

    fig = go.Figure()
    fig.add_trace(go.Scatter(x=x_axis, y=y_axis))
    fig.update_layout(title="Exception difference depends on sample size",
                      xaxis_title="Sample size",
                      yaxis_title="Exception")
    fig.show()

    # Question 3 - Plotting Empirical PDF of fitted model

    x_axis = samples
    ug.fit(x_axis)
    y_axis = ug.pdf(x_axis)

    fig = go.Figure()
    fig.add_trace(go.Scatter(x=x_axis, y=y_axis, mode="markers"))
    fig.update_layout(title="Empirical PDF of fitted model",
                      xaxis_title="Sample value",
                      yaxis_title="PDF")
    fig.show()

def test_multivariate_gaussian():
    # Question 4 - Draw samples and print fitted model
    cov = np.array([[1, 0.2, 0, 0.5],
                    [0.2, 2, 0, 0],
                    [0, 0, 1, 0],
                    [0.5, 0, 0, 1]])
    mu = [0, 0, 4, 0]

    samples = np.random.multivariate_normal(mu, cov, size=1000)
    mug = MultivariateGaussian()
    mug.fit(samples)
    print(mug.mu_, "\n", mug.cov_)

    # Question 5 - Likelihood evaluation

    mat = np.linspace(-10, 10, 200)

    likelihood_array = np.empty(shape=(mat.size, mat.size))

    max_index = [mat[0], mat[0]]
    max_arg = mug.log_likelihood(np.array([mat[0], 0, mat[0], 0]), cov,
                                 samples)

    for i in range(mat.size):
        for j in range(mat.size):
            tmp_arg = mug.log_likelihood(np.array([mat[i], 0, mat[j], 0]),
                                         cov,
                                         samples)
            if tmp_arg > max_arg:
                max_arg = tmp_arg
                max_index = [mat[i], mat[j]]
            likelihood_array[i][j] = tmp_arg

    fig = go.Figure()
    fig.add_trace(go.Heatmap(x=mat, y=mat, z=likelihood_array))
    fig.update_layout(title="logliklihood of [f1, 0, f3, 0]",
                      xaxis_title="f1 val",
                      yaxis_title="f3 val")
    fig.show()

    # Question 6 - Maximum likelihood
    print(max_arg)
    print(max_index)

if __name__ == '__main__':
    np.random.seed(0)
    test_univariate_gaussian()
    test_multivariate_gaussian()


# Authors: Dr. Clayton Morrison, Sean Hendryx

import numpy

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

def simple_quadratic_function(x):
    """
    this function accepts a 2D vector as input.
    Its outputs are:
       value: h(x1, x2) = x1^2 + 3*x1*x2
       grad: A 2x1 vector that gives the partial derivatives
             of h with respect to x1 and x2
    Note that when we pass simple_quadratic_function(x) to
    compute_gradient_numerical_estimate, we're assuming
    that compute_gradient_numerical_estimate will use only
    the first returned value of this function.
    :param x:
    :return:
    """
    value = x[0] ** 2 + 3 * x[0] * x[1]
    #print "value inside simple_quadratic_function(x): \n", value
    grad = numpy.zeros(shape=2, dtype=numpy.float32)
    grad[0] = 2 * x[0] + 3 * x[1]
    grad[1] = 3 * x[0]

    return value, grad

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

def compute_gradient_numerical_estimate(J, theta, epsilon=0.0001):
    """
    :param J: a loss (cost) function that computes the real-valued loss given parameters and data
    :param theta: array of parameters
    :param epsilon: amount to vary each parameter in order to estimate
                    the gradient by numerical difference
    :return: array of numerical gradient estimate
    """

    gradient = numpy.zeros(theta.shape)
    #print "gradient: \n", gradient

    ### YOUR CODE HERE ###

    e = numpy.zeros(theta.shape)

    # Debugging:

    #e[0] = epsilon
    #print "theta: \n", theta
    #print "e: \n", e
    #print "e[1]: \n", e[0]
    #lower = J(theta - e)
    #upper = J(theta + e)
    #print "lower: \n", lower
    #print "lower[0]: \n", lower[0]

    # How to get just first value returned from J()?
    #here

    for i in range(0,(numpy.size(theta))):
        #vector containing only epsilon at index i.  Zeros everywhere else
        e[i] = epsilon
        #print "e: \n", e
        lower = J(theta - e)
        #print "lower[0]: \n", lower[0]
        upper = J(theta + e)
        #print "upper[0]: \n", upper[0]
        #Compute numerical gradient from upper and lower:
        # just use first value of upper: upper[0]
        gradient[i] = (upper[0] - lower[0])/( 2.0 * epsilon)
        #print "gradient: \n", gradient
        #Change e[i] back to zero:
        e[i] = 0
        #print "e \n", e

    return gradient

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

def test_compute_gradient_numerical_estimate():
    """
    Test of compute_gradient_numerical_estimate.
    This provides a test for your numerical gradient implementation
    in compute_gradient_numerical_estimate
    It analytically evaluates the gradient of a very simple function
    called simple_quadratic_function and compares the result with
    your numerical estimate. Your numerical gradient implementation
    is incorrect if your numerical solution deviates too much from
    the analytical solution.
    :return:
    """
    print "test_compute_gradient_numerical_estimate(): Start Test"
    print "    Testing that your implementation of "
    print "        compute_gradient_numerical_estimate()"
    print "        is correct"
    x = numpy.array([4, 10], dtype=numpy.float64)
    (value, grad) = simple_quadratic_function(x)

    print "    Computing the numerical and actual gradient for 'simple_quadratic_function'"
    num_grad = compute_gradient_numerical_estimate(simple_quadratic_function, x)
    print "    The following two 2d arrays should be very similar:"
    print "        ", num_grad, grad
    print "    (Left: numerical gradient estimate; Right: analytical gradient)"

    diff = numpy.linalg.norm(num_grad - grad)
    print "    Norm of the difference between numerical and analytical num_grad:"
    print "        ", diff
    print "    (should be < 1.0e-09 ; I get about 1.7e-10)"
    print "test_compute_gradient_numerical_estimate(): DONE\n"


"""
Author: Peratham Wiriyathammabhum

"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
eps = np.finfo(float).eps

def ridge_regression(Xin, yin, opts, thres=10**-5, max_epochs=200):
	"""
	Perform ridge regression on an input row matrix X and a target vector y.
	See: http://cs229.stanford.edu/notes-spring2019/cs229-notes1.pdf
		 http://ciml.info/dl/v0_99/ciml-v0_99-ch07.pdf
		 http://cazencott.info/dotclear/public/lectures/ma2823_2017/slides/iml_chap7_regularization.pdf
	"""
	X = Xin.copy() # safety
	y = yin.copy()
	y = np.expand_dims(y,axis=1)

	ni, nd = X.shape
	X = np.append(X, np.ones((ni, 1)), axis=1) # append the bias/const term

	# theta = weight_init(nd, init_mode='random')

	lr = opts['lr']
	bsize = opts['bsize']
	lamb_const = opts['lamb_const']

	"""
	theta = X^{-1} * y. use a pseudo inverse for X^{-1}.
	Solve the normal equation X'X\theta = X'y.
	"""
	theta = np.matmul(np.matmul(np.linalg.pinv(np.matmul(X.T, X) + lamb_const*np.diag(np.ones(nd+1))), X.T), y)
	# theta = np.matmul(np.linalg.pinv(X), y)
	pred = np.matmul(X, theta)
	loss = square_loss(pred, y, theta, lamb_const)
	print('[Info] loss: {:.4f}'.format(float(loss)))
	return theta, pred

def weight_init(nd, init_mode='random'):
	if init_mode == 'random':
		theta = np.random.randn(nd + 1, 1) # plus the bias/const term
	elif init_mode=='xavier':
		"""
		Glorot, Xavier, and Yoshua Bengio.
		"Understanding the difficulty of training deep feedforward neural networks."
		Proceedings of the thirteenth international conference on artificial
		intelligence and statistics. 2010.
		"""
		pass
	else:
		pass
	return theta

def square_loss(pred, yin, theta, lamb_const):
	"""
	A typical L2 norm aka. square error.
	"""
	y = yin.copy()
	return np.linalg.norm(pred - y, ord=2) + (lamb_const / 2.) * np.linalg.norm(theta, ord=2)

def terminating_cond(nite, max_iters):
	if nite >= max_iters:
		print('[Info] terminate at iter: {}'.format(nite))
		return True
	# elif np.linalg.norm(mu - nmu) < thres:
	#   print('[Info] terminate at iter: {}'.format(nite))
	#   return True
	else:
		return False

def main(opts):
	max_epochs = opts['max_epochs']
	from sklearn import datasets
	diabetes = datasets.load_diabetes()
	X, y = diabetes.data, diabetes.target
	# whitening
	X = (X - np.mean(X, axis=0)) / np.std(X, axis=0)

	theta, pred = ridge_regression(X[:, np.newaxis, 2], y, opts, thres=10**-5, max_epochs=max_epochs)
	y = np.expand_dims(y,axis=1)
	# plot
	# input("Press Enter to continue...")
	"""
	plotting data and predictions as well as regression lines.
	"""
	diabetes_X = X[:, np.newaxis, 2]
	idx = np.argsort(diabetes_X, axis=0)
	plt.scatter(diabetes_X[idx].squeeze(1), y[idx].squeeze(1),  color='black')
	plt.plot(diabetes_X[idx].squeeze(1), pred[idx].squeeze(1), color='blue', linewidth=3)

	plt.xticks(())
	plt.yticks(())

	plt.show()
	input("Press Enter to continue...")
	plt.close()
	"""
	plotting model parameters using hinton diagram
	"""
	from hinton_diagram import hinton
	h_theta = theta.copy() # safety for plotting
	hinton(h_theta)
	plt.show()
	input("Press Enter to continue...")
	plt.close()
	"""
	TODO: plotting the solution path
	"""
	return

if __name__ == '__main__':
	import argparse

	parser = argparse.ArgumentParser(description='run ridge regression.')
	parser.add_argument('--lr', dest='lr',
					  help='learning rate',
					  default=1e-3, type=float)
	parser.add_argument('--bsize', dest='bsize',
					  help='batch size',
					  default=16, type=int)
	parser.add_argument('--lambda', dest='lamb_const',
					  help='lambda for l2',
					  default=1e-3, type=float)
	parser.add_argument('--max_epochs', dest='max_epochs',
					  help='number of iterations to train',
					  default=200, type=int)
	args = parser.parse_args()
	opts = vars(args)

	main(opts)


# -*- coding: utf-8 -*-
"""
Created on Fri Nov 13 17:03:59 2020

Questão 5
Código desenvolvido para calcular o valor médio e RMS de sinais periódicos

@author: Bianca
"""
# importar as bibliotecas necessárias
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams.update({'font.size': 14})

Tp = 1 #período
f = 1 #frequência
t = np.linspace(0, Tp, 1000) #vetor temporal
A = 12 #amplitude
w = 2*np.pi*f #omega
xt = A*np.sin(w*t) #equação do sinal

x0 = np.mean(xt) #valor médio do sinal
xrms = np.sqrt(np.mean(xt**2)) #valor rms do sinal

print(f'Valor Médio: {x0:.3f}')
print(f'Valor RMS: {xrms:.3f}')

#plotando o gráfico
plt.figure()
plt.title('Sinal do Valor médio e RMS (i)')
plt.plot(t, xt, 'b', label = r'$x(t)$')
plt.plot(t,x0*np.ones(len(t)),'--k', label = r'$x_0$')
plt.plot(t,xrms*np.ones(len(t)),'--r', label = r'$x_{rms}$')
plt.legend(loc = 'upper right')
plt.grid(linestyle = '--', which='both')
plt.xlabel('tempo [s]')
plt.ylabel('Amplitude [-]')
plt.tight_layout()
plt.show()

#%% (ii)

Tp = 1 #período
f = 1 #frequência
t = np.linspace(0, Tp, 1000) #vetor temporal
A = 8 #amplitude do sinal
A0 = 3 #deslocamento do sinal
w = 2*np.pi*f #omega
xt = A0 + A*np.sin(w*t) #equação do sinal

x0 = np.mean(xt) #valor médio do sinal
xrms = np.sqrt(np.mean(xt**2)) #valor rms do sinal

print(f'Valor Médio: {x0:.3f}')
print(f'Valor RMS: {xrms:.3f}')

#plotando o gráfico
plt.figure()
plt.title('Sinal do Valor médio e RMS (ii)')
plt.plot(t, xt, 'b', label = r'$x(t)$')
plt.plot(t,x0*np.ones(len(t)),'--k', label = r'$x_0$')
plt.plot(t,xrms*np.ones(len(t)),'--r', label = r'$x_{rms}$')
plt.legend(loc = 'upper right')
plt.grid(linestyle = '--', which='both')
plt.xlabel('tempo [s]')
plt.ylabel('Amplitude [-]')
plt.tight_layout()
plt.show()


import numpy as np

def tree_valley_function(x):
    """
    Represents a two dimensional Function, with two local and one global minima
    :param x: 1D Rowvector (multiple Values can be passed)
    :type x: Numpy array
    :return: Function values of x
    :rtype: Numpy array
    """

    y = -(
        np.exp(-((x - 2) ** 2))
        + np.exp(-((x - 6) ** 2) / 10)
        + 1 / (x ** 2 + 1)
    )

    return y

def four_humps_function(x):
    """
    Function with one global maxima, one local maximum, one glomal minimun an
    a locam minimum
    :param x: 2D rowvector (a column of rows can be passed)
    :type x: Numpy array
    :return: Function values of x
    :rtype: Numpy array
    """

    y = (1 - x[:, 0] / 2 + x[:, 0] ** 5 + x[:, 1] ** 3) * np.exp(
        -x[:, 0] ** 2 - x[:, 1] ** 2
    )

    return y

def himmelblaus_function(x):
    """
    The Himmelblau Function is a multi-modal function, with one local maximum
    four local minima.
    :param x: 2D rowvector (a column of rows can be passed)
    :type x: Numpy array
    :return: Function values of x
    :rtype: Numpy array
    """

    y = (x[:, 0] ** 2 + x[:, 1] - 11) ** 2 + (x[:, 0] + x[:, 1] ** 2 - 7) ** 2

    return y

def rotated_hyper_ellipsoid_function(x):
    """
    The Rotated Hyper-Ellipsoid function is continuous, convex and unimodal.
    It is an extension of the Axis Parallel Hyper-Ellipsoid function,
    also referred to as the Sum Squares function.
    :param x: N-Dimensional Vector rowvector (a column of rows can be passed)
    :type x: Numpy array
    :return: Function values of x
    :rtype: Numpy Array
    """
    x = np.atleast_2d(x)
    y = np.zeros(x.shape[0])
    for i in range(x.shape[1]):
        y = y + x[:, i] ** 2

    return y

def matyas_function(x):
    """
    The Matyas function has no local minima except the global one.
    :param x: 2D rowvector (a column of rows can be passed)
    :type x: Numpy array
    :return: Function values of x
    :rtype: Numpy array
    """

    y = 0.26 * (x[:, 0] ** 2 + x[:, 1] ** 2) - 0.48 * x[:, 0] * x[:, 1]

    return y

def cross_in_tray_function(x):
    """
    The Cross-in-Tray function has multiple global minima. It is shown here
    with a smaller domain in the second plot, so that its characteristic
    "cross" will be visible.
    :param x: 2D rowvector (a column of rows can be passed)
    :type x: Numpy array
    :return: Function values of x
    :rtype: Numpy array
    """

    fact1 = np.sin(x[:, 0]) * np.sin(x[:, 1])
    fact2 = np.exp(np.abs(100 - np.sqrt(x[:, 0] ** 2 + x[:, 1] ** 2) / np.pi))
    y = -0.0001 * (abs(fact1 * fact2) + 1) ** 0.1
    return y

def SixHumpCamelFunction(x):
    """
    he plot on the left shows the six-hump Camel function on its recommended
    input domain, and the plot on the right shows only a portion of this
    domain, to allow for easier viewing of the function's key characteristics.
    The function has six local minima, two of which are global.
    :param x: 2D rowvector (a column of rows can be passed)
    :type x: Numpy array
    :return: function values of x
    :rtype: Numpy array
    """

    x1 = x[:, 0]
    x2 = x[:, 1]
    y = (
        (4 - 2.1 * x1 ** 2 + (x1 ** 4) / 3) * x1 ** 2
        + x1 * x2
        + (-4 + 4 * x2 ** 2) * x2 ** 2
    )
    return y

def michalewicz_function(x):
    """
    The Michalewicz function has d! local minima, and it is multimodal.
    The parameter m defines the steepness of they valleys and ridges;
    a larger m leads to a more difficult search.
    The recommended value of m is m = 10.
    :param x: 2D rowvector (a column of rows can be passed)
    :type x: Numpy array
    :return: Function Values of x
    :rtype: Numpy array
    Funktioniert nicht so wie es soll
    Quelle:
        http://www.sfu.ca/~ssurjano/michal.html
    """

    m = 10
    y = np.zeros(x.shape[0])
    for i in range(x.shape[1]):
        y = y + np.sin(x[:, i]) * (np.sin(i * x[:, i] ** 2 / np.pi)) ** (2 * m)
    return -y

def rastrigin_function(x):
    """
    The Rastrigin function has several local minima. It is highly multimodal,
    but locations of the minima are regularly distributed.
    :param x: 2D rowvector (a column of rows can be passed)
    :type x: Numpy array
    :return: Function Values of x
    :rtype: Numpy array
    """

    dim = x.shape[1]
    y = np.zeros(x.shape[0])
    for i in range(x.shape[1]):
        y = y + x[:, i] - 10 * np.cos(2 * np.pi * x[:, i])
    return 10 * dim + y


#importing necessary libraries
import numpy as np
import matplotlib.pyplot as plt
import time
from mpl_toolkits import mplot3d
import pandas as pd
import argparse

def compute_cost(X,Y,theta):
    '''
    Input: X= design matrix of dimension (no of samples, no of features+1)
           Y= response vector of dimension (no of samples,1)
           theta= parameter vector of dimension (no of parameters,1)
    Outpt: cost= Mean squared error, a scalar
    '''
    #dimension check in case of SGD
    if(X.ndim==1):
        X=X.reshape(1,-1)
        Y=Y.reshape(-1,1)

    cost=np.sum(np.power(Y-np.dot(X,theta),2),axis=0,keepdims=True)*(1/(2*X.shape[0]))
    return cost

def update_parameters(X,Y, theta, learning_rate):
    '''
    Input: X= design matrix of dimension (no of samples, no of features+1)
           Y= response vector of dimension (no of samples,1)
           theta= parameter vector of dimension (no of parameters,1)
           learning_rate=a scalar
    Outpt: theta= updated parameter vector of dimension (no of parameters,1)
    '''
    if(X.ndim==1):
        X=X.reshape(1,-1)
        Y=Y.reshape(-1,1)
    error=Y-np.dot(X,theta)
    theta=theta+learning_rate*(1/X.shape[0])*np.transpose(np.sum(error*X,axis=0,keepdims=True))
    return theta

def plot_graph(values,elev,azim):
    '''
    Input: values= a dictionary conaying theta and theta_array
           elev= 3d surface vertical angle view
           azim=3d surface x-y plane angle view
    '''
    fig = plt.figure(figsize = (18, 10))
    ax = plt.axes(projection ="3d")
    x=values["theta_array"][:,0]
    y=values["theta_array"][:,1]
    z=values["theta_array"][:,2]
    ax.scatter3D(x, y, z, color = "green",s=2)
    ax.view_init(elev,azim)
    plt.xlabel('x-axis')
    plt.ylabel('y-axis')
    ax.set_zlabel("z-axis")
    plt.title("3D parameter plot")
    plt.show()

def stochastic_gradient_descent(X,Y,batch_size,eps):

    if batch_size==1:
        k=100
        epochs=30000
    elif batch_size==100:
        k=5
        epochs=20000
    elif batch_size==10000:
        k=2
        epochs=22000
    elif batch_size==1000000:
        k=1
        epochs=25000

    print("Batch_size: "+str(batch_size))

    learning_rate=0.001
    theta=np.array([0,0,0]).reshape(-1,1)
    old_cost_array=np.array([])
    new_cost_array=np.array([])
    theta_array=np.array([])
    step=0

    old_cost_array=np.append(new_cost_array,compute_cost(X[0,:].reshape(1,-1), Y[0].reshape(-1,1),theta))

    flag=True
    start=time.time()
    while flag:
        for i in range(int(X.shape[0]/batch_size)):
            #print(int(X.shape[0]/batch_size))
            X_data=X[i*batch_size:(i+1)*batch_size,:]
            Y_data=Y[i*batch_size:(i+1)*batch_size]

            step=step+1
            theta=update_parameters(X_data,Y_data,theta,learning_rate)
            theta_array=np.append(theta_array,theta)

            new_cost=compute_cost(X_data,Y_data,theta)
            new_cost_array=np.append(new_cost_array,new_cost)
            if (step%500==0):
                print("Parameters are: \n"+str(theta))

            if (step%k==0):
                if(np.abs(np.mean(old_cost_array)-np.mean(new_cost_array))<eps or step>=epochs):
                    flag=False
                    end=time.time()
                    time_taken=end-start
                    print("Error difference: "+str(np.abs(np.mean(old_cost_array)-np.mean(new_cost_array))))
                    print("Error is: "+str(np.mean(new_cost_array)))
                    print("No of. iterations is: "+str(step))
                    print("Time taken: "+str(end-start))
                    print("Parameters are:\n "+str(theta))
                    break
                old_cost_array=new_cost_array
                new_cost_array=np.array([])

    theta_array=theta_array.reshape(-1,theta.shape[0])
    values={"theta_array":theta_array,"theta":theta}
    return values

#generates the same random numbers in each iteration
np.random.seed(0)

#generating normal random variables with specified mean and variance
X1=np.random.normal(3,2,(1000000,1))
X2=np.random.normal(-1,2,(1000000,1))

#X is the design matrix
X=np.hstack((np.ones(X1.shape),X1,X2))

parameters=np.array((3,1,2)).reshape(-1,1)

#Y is the response vector
Y=np.dot(X,parameters)+np.random.normal(0,np.sqrt(2),(1000000,1))

parser = argparse.ArgumentParser()
parser.add_argument('--bs',type = int)
parser.add_argument('--eps',type = float)
args = parser.parse_args()

batch_size = args.bs
eps = args.eps

values=stochastic_gradient_descent(X,Y,batch_size,eps)

#loading the test data
test_data=pd.read_csv('q2test.csv').to_numpy()
#print(test_data.shape)
testX=test_data[:,0:2]
#print(testX.shape)
testX=np.hstack((np.ones((test_data.shape[0],1)),testX))
testY=test_data[:,2].reshape(-1,1)

error1=2*compute_cost(testX,testY,parameters)
error2=2*compute_cost(testX,testY,values["theta"])
print("Error wrt the learned hypothesis is: "+str(error2))
print("Error wrt the original hypothesis is: "+str(error1))
plot_graph(values,elev=10,azim=100)


"""
Test implementation of Euler's method
Srayan Gangopadhyay
2020-05-13
"""

import numpy as np
import matplotlib.pyplot as plt

def func(y):  # RHS of ODE in form dy/dx=
    return y

# PARAMETERS
y0 = 1  # initial value
delta = 1  # step size
end = 3  # x-value to stop integration

steps = int(end/delta)  # number of steps
x = np.linspace(0, end, steps+1)  # array of x-values
y = np.zeros(steps+1)  # empty array for solution
y[0] = y0  # inserting initial value

# INTEGRATING
for i in range(1, steps+1):
    y[i] = y[i-1] + (delta*func(y[i-1]))

plt.plot(x, y, label='Approx. soln (Euler)')
plt.plot(x, y, 'o')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.show()


"""
 modelize plan trajectory with spline

Speed modeled as follow:
 - sin at beginning (0 to Vc)
 - vc
 - sin at end (Vc to 0)

Trajectory is derived by taking integral
"""

import numpy as np

tf = 10

t1 = 3
t2 = tf - t1
w = np.pi/t1
p1 = np.pi + np.pi/2. # phase so we can use only sin
p2 = np.pi/2.

# average speed / cruise speed
vc = 1000

# generic sin function so it does not need to be rewritten
def genSin(t, w, p, vc):
  return vc/float(2)*(np.sin(w*t+p)+1)

# integral primitive of previous function
def genSinPrim(t, w, p, vc):
  return vc/float(2)*(-np.cos(w*t+p)/w+t)

def f1(t):
  return genSin(t, w, p1, vc)

def f2(t):
  return vc

def f3(t):
  return genSin(t-t2, w, p2, vc)

def f(t):
  if t < 0:
    return 0
  if t < t1:
    return f1(t)
  elif t < t2:
    return f2(t)
  elif t < tf:
    return f3(t)
  else:
    return 0

######### calculates integrals ########

def f1Prim(t):
  return genSinPrim(t, w, p1, vc)

def f2Prim(t):
  return vc*(t-t1) + f1Prim(t1)

def f3Prim(t):
  return genSinPrim(t-t2, w, p2, vc) + f2Prim(t2)

def fPrim(t):
  if t1> tf/float(2):
    0
  else:
    if t < 0:
      return 0
    if t < t1:
      return f1Prim(t)
    elif t < t2:
      return f2Prim(t)
    elif t < tf:
      return f3Prim(t)
    else:
      return f3Prim(tf)

# compute integral numerically
def fNumPrim(t):
  N = 300  # number of iteration per call (very computational intensive)
  x = np.linspace(0,t,N)
  r = 0.
  dx = float(t)/float(N)
  for i in x:
    r = r + dx*g(i)

  return r

# piece of magic here
# http://scicomp.stackexchange.com/questions/1144/how-can-i-plot-piece-wise-defined-function-in-some-easily-accessed-open-source-t?newreg=3991fda29a504478a7aca2bb58e61b96
g = np.vectorize(f)
gPrim = np.vectorize(fPrim)
#gNumPrim = np.vectorize(fNumPrim)

import matplotlib.pyplot as plt

t = np.linspace(0, tf, 300)

plt.plot(t, g(t))

#plt.plot(t, gNumPrim(t))
plt.plot(t, gPrim(t))

plt.xlabel('time [s]')
plt.ylabel('trajectory [m]')
plt.legend()
plt.savefig("ballistic.png")
plt.show()


from sympy import mod_inverse

class ModArith:
    def __init__(self, val, mod):
        if isinstance(val, ModArith):
            assert(mod == val.m)
            self.val = val.val
            self.m = mod
        else:
            self.val = val % mod
            self.m = mod

    def __add__(self, other):
        assert(isinstance(other, ModArith))
        assert(self.m == other.m)
        return ModArith(self.val+other.val, self.m)

    def __sub__(self, other):
        assert(isinstance(other, ModArith))
        assert(self.m == other.m)
        return ModArith(self.m+self.val-other.val, self.m)

    def __mul__(self, other):
        assert(isinstance(other, ModArith))
        assert(self.m == other.m)
        return ModArith(self.val*other.val, self.m)

    def __divmod__(self, other):
        assert(isinstance(other, ModArith))
        assert(self.m == other.m)
        quot, rem = 0, self.val
        while rem >= other.val:
            rem = rem - other.val
            quot = quot+1
        return (ModArith(quot, self.m), ModArith(rem, self.m))

    def __eq__(self, other):
        assert(isinstance(other, ModArith))
        assert(self.m == other.m)
        return self.val == other.val

    def __neg__(self):
        return ModArith(self.m-self.val, self.m)

    def iszero(self):
        return self.val == 0

    def __repr__(self):
        return f"{self.val}"

    def zero(self):
        return ModArith(0, self.m)

    def inv(self):
        return ModArith(mod_inverse(self.val, self.m), self.m)

import itertools

def add_(a, b):
    if a is None: return b
    if b is None: return a
    return a+b

def sub_(a, b):
    if a is None: return -b
    if b is None: return a
    return a-b

class Poly:
    def __init__(self, c, ctor):
        self.ctor = ctor
        self.c = [ctor(i) for i in c]
        if len(self.c) > 0:
            while len(self.c) and self.c[0].iszero():
                self.c.pop(0)
            if len(self.c) > 0:
                self.order=len(self.c)-1
            else:
                self.order=0
        else:
            self.order=0

    def scale(self, x):
        return Poly([c*x for c in self.c], self.ctor)

    def __add__(self, other):
        cs = list(add_(a,b) for a, b in itertools.zip_longest(reversed(self.c), reversed(other.c)))
        return Poly(list(reversed(cs)), self.ctor)

    def __sub__(self, other):
        cs = list(sub_(a,b) for a, b in itertools.zip_longest(reversed(self.c), reversed(other.c)))
        return Poly(list(reversed(cs)), self.ctor)

    def __mul__(self, other):
        acc = Poly([], self.ctor)
        x = self
        for c in reversed(other.c):
            acc = acc + x.scale(c)
            x = Poly(x.c+[c.zero()], self.ctor)
        return acc

    def __mod__(self, other):
        return divmod(self, other)[1]

    def __neg__(self):
        return Poly([-c for c in self.c], self.ctor)

    def __divmod__(self, other):
        rem = self
        quot = Poly([], self.ctor)
        while len(rem.c) >= len(other.c):
            r = rem.c[0]
            o = other.c[0]
            order = len(rem.c) - len(other.c)
            c = r * o.inv()
            q = Poly([c]+order*[self.ctor(0)], self.ctor)
            quot = quot + q
            rem = rem - q*other
        return (quot, rem)

    def __repr__(self):
        if len(self.c) == 0: return "0"
        return ' + '.join(reversed([f"{c} x^{n}" if n > 0 else f"{c}" for n, c in enumerate(reversed(self.c))]))

M = lambda x: ModArith(x, 7)
P = lambda x: Poly(list(map(M, x)), M)

a = P([1,2,1,0])
b = P([0,2,0,0])
str(a)
str(b)
str(divmod(a,b))

# In[0]

from hamming2 import allVectors
import numpy as np

def cycCode(gc, k, base):
    M = lambda x: ModArith(x, base)
    P = lambda x: Poly(x, M)
    g = P(gc)
    r = g.order
    n = k+r
    xr = P([1]+r*[0])

    def encode(m):
        z = P(m)*xr
        z = z - (z % g)
        cs = [c.val for c in z.c]
        while len(cs) < n:
            cs.insert(0, 0)
        return cs

    d = min(np.count_nonzero([i for i in encode(m)]) for m in itertools.islice(allVectors(k, base), 1, None))
    ec = (d-1)//2
    print(f"Code distance is {d}, can correct {ec} errors")

    tbl = {}
    for ei in allVectors(n, base):
        if 0 < np.count_nonzero(ei) <= ec:
            si = P(ei) % g
            tbl[str(si)] = np.reshape(ei, n)
    print(f"Table size is {len(tbl)}")

    def decode(c):
        s = P(c) % g
        if all(c.iszero() for c in s.c):
            return c[:k]
        e = tbl[str(s)]
        c1 = c - e
        return c1[:k]

    return (encode, decode)

encode, decode = cycCode(gc=[1,0,1,0,0,1,1,1], k=4, base=4)

# In[0]

c = encode([1,2,3,0])
c[3] += 2
c[5] += 2
decode(c)


import numpy as np
import pandas as pd

'''
     Gini impurity is a measure of how often a randomly
     chosen element from the set would be incorrectly labeled
     if it was randomly labeled according to the distribution
     of labels in the subset.
'''

# Read example from csv
#example = "ImpurityMeasures/example.csv"
example = "ImpurityMeasures/example2.csv"
data = pd.read_csv(example)

# The fastest way to find occurences within an array
# Check it out in this stackoverflow thread:
# https://stackoverflow.com/questions/10741346/numpy-most-efficient-frequency-counts-for-unique-values-in-an-array
def unique_count(A):
    unique, inverse = np.unique(A, return_inverse=True)
    count = np.zeros(len(unique), dtype=int)
    np.add.at(count, inverse, 1)
    return np.vstack((unique, count)).T

# Get the gini impurity of one node
def gini(N):
    uniques = unique_count(N)
    total = len(N)
    # Probability of every node leaf Pi
    Pi = np.zeros(uniques.shape[0])
    for i in range(uniques.shape[0]):
        pi = uniques[i,1]/total
        Pi[i] = pi

    # Appliying the Gini formula: 1 - Sum[Pi(t)^2]
    gini = 1 - np.sum(Pi**2)

    print(uniques)
    print("Probabilities of Pi: ", Pi)
    print("Gini impurity: {}\n".format(gini))
    return gini

# Finding the purest node within data
def findPurest():
    purest = {
        "column": "",
        "gini": 1
    }

    for node in data:
        print("------------ {} ------------".format(node))
        gini_ = gini(data[node].values)

        if gini_ < purest['gini']:
            purest['column'] = node
            purest['gini'] = gini_

    print("The purest node is: {} \nWith an gini index: {}"
    .format(purest['column'], purest['gini']))

    return purest['gini']

# You can select the Father node
#father = findPurest() # is the purest node
father = "Clase"
child = "Talla_Camisa"

def giniWeighted():
    #
    crosstab = pd.crosstab(data[child], data[father], margins=True, margins_name="Total")
    print(crosstab,'\n')
    index = crosstab.index
    crosstab = crosstab.values
    # Sum the ginis
    giniW = 0

    for i in range(len(index)-1):
        print("--------------- {} ---------------".format(index[i]))
        pi = crosstab[i,:-1]/crosstab[i,-1]
        gini_ = 1 - np.sum(pi**2)

        # Calculate the weighted gini and sum
        giniW += (crosstab[i,-1]/crosstab[-1,-1])*gini_

        print("Probabilities of Pi: {}\nGini impurity: {}\n"
        .format(pi, gini_))

    print("The weighted gini is: ", giniW)
    return giniW

giniW = giniWeighted()


import math

# import numpy as np
# from sympy import Symbol, integrate, lambdify, solve, re
print("Квадратурные формулы наивысшей алгебраической степени точности (формулы типа Гаусса)\nВариант 5\n")

# Весовая функция p(x)
def p(y):
    return 1 / y ** (1 / 3)

# Функция f(x)
def f(y):
    return math.cos(y)

# Функция phi(x) = p(x)f(x)
def phi(y):
    return f(y) * p(y)

# Моменты
def m(i_, a_, b_):
    if i_ == 0:
        return 3 / 2 * (b_ ** (2 / 3) - a_ ** (2 / 3))  # m_0(x)
    if i_ == 1:
        return 3 / 5 * (b_ ** (5 / 3) - a_ ** (5 / 3))  # m_1(x)

# Накопленные произведения для Лагранжа
def mult(k, z):
    t = 1
    for p in range(m + 1):
        if p != k:
            t = t * (z - values[p])
    return t

# Метод Лагранжа
def lagrange(q):
    ans = 0
    for t in range(m + 1):
        ans = ans + (f(values[t]) * mult(t, q) / mult(t, values[t]))
    return ans

# КФ средних прямоугольников
def mid():
    s = 0
    for j in range(m):
        s = s + phi(values[j + 1] - h / 2)

    return h * s

# Формула Гаусса
def gauss():
    global values
    values = [-(0.6 ** 0.5), 0, 0.6 ** 0.5]
    p_ = [-0.4, -0.5, 0.4]
    a_ = []
    for j in range(m + 1):
        # print(j, values[j], p_[j])
        a_.append((2 * (1 - values[j] ** 2)) / (((m + 1) ** 2) * p_[j] ** 2))

    sum_ = 0
    for j in range(m + 1):
        # print(a_[j], phi((b - a) * values[j] / 2 + (b + a) / 2))
        sum_ += a_[j] * phi((b - a) * values[j] / 2 + (b + a) / 2)
    return ((b - a) / 2) * sum_

r = 1
while r:
    print("Функция phi(x) = cos(x)/x^(1/3):\np(x) = 1/x^(1/3), f(x) = cos(x)\n")
    print("Нижний предел интегрирования, A = 0")
    a = 0
    print("Верхний предел интегрирования, B = 1")
    b = 1
    print("Число промежутков деления, m:")
    m = int(input())

    values = []
    h = (b - a) / m  # длина частичного разбиения

    # Массив узлов
    for i in range(m + 1):
        xi = (a + i * h)
        values.append(xi)

    print('h =', h)

    # точный интеграл phi от 0 до 1
    j_ = 1.3212230741459003099469074212370546202293316300043820048748526319098001457707427010561826248610759312

    print('Точное значение интеграла J =', j_)
    int_ = mid()

    print('\nМетод средних прямоугольников, J(h):', int_)
    print('Фактическая погрешность, |J - J(h)|:', abs(j_ - int_))

    # print(lagrange(0))
    values = [1 / 4, 3 / 4]
    a_0 = (3 / 5 - 3 / 2 * values[1]) / (values[0] - values[1])
    a_1 = (3 / 5 - 3 / 2 * values[0]) / (values[1] - values[0])
    int_ = a_0 * f(values[0]) + a_1 * f(values[1])

    # print('\nУзлы: 1/4, 3/4')
    print('\nИнтерполяционный метод, J(h):', int_)
    print('Фактическая погрешность, |J - J(h)|:', abs(j_ - int_))

    int_ = gauss()
    print('\nФормула Гаусса, J(h):', int_)
    print('Фактическая погрешность, |J - J(h)|:', abs(j_ - int_))


import numpy as np

# Define the gaussian coefficients for collocation points

class gauss:

    # Input: n - stages of the gaussian coefficients
    def __init__(self, n):
        self.n = n

        if n == 2:
            x1 = 0.577350269189626
            w1 = 1.000000000000000
            t = [(-x1+1)*0.5, (x1+1)*0.5]
            w = [w1*0.5, w1*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)
        elif n == 3:
            x0 = 0.000000000000000
            x1 = 0.774596669241483
            w0 = 0.888888888888889
            w1 = 0.555555555555556
            t = [(-x1+1)*0.5, 0.5, (x1+1)*0.5]
            w = [w1*0.5, w0*0.5, w1*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)
        elif n == 4:
            x1 = 0.339981043584856
            x2 = 0.861136311594053
            w1 = 0.652145154862546
            w2 = 0.347854845137454
            t = [(-x2+1)*0.5, (-x1+1)*0.5, (x1+1)*0.5, (x2+1)*0.5]
            w = [w2*0.5, w1*0.5,  w1*0.5, w2*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)
        elif n == 5:
            x0 = 0.000000000000000
            x1 = 0.538469310105683
            x2 = 0.906179845938664
            w0 = 0.568888888888889
            w1 = 0.478628670499366
            w2 = 0.236926885056189
            t = [(-x2+1)*0.5, (-x1+1)*0.5, 0.5, (x1+1)*0.5, (x2+1)*0.5]
            w = [w2*0.5, w1*0.5, w0*0.5, w1*0.5, w2*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)
        elif n == 6:
            x1 = 0.238619186083197
            x2 = 0.661209386466265
            x3 = 0.932469514203152
            w1 = 0.467913934572691
            w2 = 0.360761573048139
            w3 = 0.171324492379170
            t = [(-x3+1)*0.5, (-x2+1)*0.5, (-x1+1)*0.5, (x1+1)*0.5, (x2+1)*0.5, (x3+1)*0.5]
            w = [w3*0.5, w2*0.5, w1*0.5,  w1*0.5, w2*0.5, w3*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)
        elif n == 7:
            x0 = 0.000000000000000
            x1 = 0.405845151377397
            x2 = 0.741531185599394
            x3 = 0.949107912342759
            w0 = 0.417959183673469
            w1 = 0.381830050505119
            w2 = 0.279705391489277
            w3 = 0.129484966168870
            t = [(-x3+1)*0.5, (-x2+1)*0.5, (-x1+1)*0.5, 0.5, (x1+1)*0.5, (x2+1)*0.5, (x3+1)*0.5]
            w = [w3*0.5, w2*0.5, w1*0.5,  w0*0.5, w1*0.5, w2*0.5, w3*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)
        elif n == 8:
            x1 = 0.183434642495650
            x2 = 0.525532409916329
            x3 = 0.796666477413627
            x4 = 0.960289856497536
            w1 = 0.362683783378362
            w2 = 0.313706645877887
            w3 = 0.222381034453374
            w4 = 0.101228536290376
            t = [(-x4+1)*0.5, (-x3+1)*0.5, (-x2+1)*0.5, (-x1+1)*0.5, (x1+1)*0.5, (x2+1)*0.5, (x3+1)*0.5, (x4+1)*0.5]
            w = [w4*0.5, w3*0.5, w2*0.5, w1*0.5,  w1*0.5, w2*0.5, w3*0.5, w4*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)
        elif n == 9:
            x0 = 0.000000000000000
            x1 = 0.324253423403809
            x2 = 0.613371432700590
            x3 = 0.836031107326636
            x4 = 0.968160239507626
            w0 = 0.330239355001260
            w1 = 0.312347077040003
            w2 = 0.260610696402935
            w3 = 0.180648160694857
            w4 = 0.081274388361574
            t = [(-x4+1)*0.5, (-x3+1)*0.5, (-x2+1)*0.5, (-x1+1)*0.5, 0.5, (x1+1)*0.5, (x2+1)*0.5, (x3+1)*0.5, (x4+1)*0.5]
            w = [w4*0.5, w3*0.5, w2*0.5, w1*0.5,  w0*0.5, w1*0.5, w2*0.5, w3*0.5, w4*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)
        elif n == 10:
            x1 = 0.148874338981631
            x2 = 0.433395394129247
            x3 = 0.679409568299024
            x4 = 0.865063366688985
            x5 = 0.973906528517172
            w1 = 0.295524224714753
            w2 = 0.269266719309996
            w3 = 0.219086362515982
            w4 = 0.149451349150581
            w5 = 0.066671344308688
            t = [(-x5+1)*0.5, (-x4+1)*0.5, (-x3+1)*0.5, (-x2+1)*0.5, (-x1+1)*0.5, (x1+1)*0.5, (x2+1)*0.5, (x3+1)*0.5, (x4+1)*0.5, (x5+1)*0.5]
            w = [w5*0.5, w4*0.5, w3*0.5, w2*0.5, w1*0.5,  w1*0.5, w2*0.5, w3*0.5, w4*0.5, w5*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)
        elif n == 11:
            x0 = 0.000000000000000
            x1 = 0.269543155952345
            x2 = 0.519096129110681
            x3 = 0.730152005574049
            x4 = 0.887062599768095
            x5 = 0.978228658146057
            w0 = 0.272925086777901
            w1 = 0.262804544510247
            w2 = 0.233193764591990
            w3 = 0.186290210927734
            w4 = 0.125580369464905
            w5 = 0.055668567116174
            t = [(-x5+1)*0.5, (-x4+1)*0.5, (-x3+1)*0.5, (-x2+1)*0.5, (-x1+1)*0.5, 0.5, (x1+1)*0.5, (x2+1)*0.5, (x3+1)*0.5, (x4+1)*0.5, (x5+1)*0.5]
            w = [w5*0.5, w4*0.5, w3*0.5, w2*0.5, w1*0.5,  w0*0.5, w1*0.5, w2*0.5, w3*0.5, w4*0.5, w5*0.5]
            self.t = np.array(t, dtype=np.float64)
            self.w = np.array(w, dtype=np.float64)


# Neural Networks and the Simplist XOR Problem
- This was adopted from the PyTorch Tutorials.
- Simple supervised machine learning.
- http://pytorch.org/tutorials/beginner/pytorch_with_examples.html

## Neural Networks
- Neural networks are the foundation of deep learning, which has revolutionized the

```In the mathematical theory of artificial neural networks, the universal approximation theorem states[1] that a feed-forward network with a single hidden layer containing a finite number of neurons (i.e., a multilayer perceptron), can approximate continuous functions on compact subsets of Rn, under mild assumptions on the activation function.```

- A simple task that Neural Networks can do but simple linear models cannot is called the [XOR problem](https://medium.com/@jayeshbahire/the-xor-problem-in-neural-networks-50006411840b).

- The XOR problem involves an output being 1 if either of two inputs is 1, but not both.

### Generate Fake Data
- `D_in` is the number of dimensions of an input varaible.
- `D_out` is the number of dimentions of an output variable.
- Here we are learning some special "fake" data that represents the xor problem.
- Here, the dv is 1 if either the first or second variable is

# -*- coding: utf-8 -*-
import numpy as np

#This is our independent and dependent variables.
x = np.array([ [0,0,0],[1,0,0],[0,1,0],[0,0,0] ])
y = np.array([[0,1,1,0]]).T
print("Input data:\n",x,"\n Output data:\n",y)

### A Simple Neural Network
- Here we are going to build a neural network.
- First layer (`D_in`)has to be the length of the input.
- `H` is the length of the output.
-  `D_out` is 1 as it will be the probability it is a 1.

np.random.seed(seed=83832)
#D_in is the number of input variables.
#H is the hidden dimension.
#D_out is the number of dimensions for the output.
D_in, H, D_out = 3, 2, 1

# Randomly initialize weights og out 2 hidden layer network.
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)
bias = np.random.randn(H, 1)

### But "Hidden Layers" Aren't Hidden
- Let's take a look
- These are just random numbers.

print(w1, w2)

### Update the Weights using Gradient Decent
- Calculate the predited value
- Calculate the loss function
- Compute the gradients of w1 and w2 with respect to the loss function
- Update the weights using the learning rate

learning_rate = .01
for t in range(500):
    # Forward pass: compute predicted y
    h = x.dot(w1)

    #A relu is just the activation.
    h_relu = np.maximum(h, 0)
    y_pred = h_relu.dot(w2)

    # Compute and print loss
    loss = np.square(y_pred - y).sum()
    print(t, loss)

    # Backprop to compute gradients of w1 and w2 with respect to loss
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.T.dot(grad_y_pred)
    grad_h_relu = grad_y_pred.dot(w2.T)
    grad_h = grad_h_relu.copy()
    grad_h[h < 0] = 0
    grad_w1 = x.T.dot(grad_h)

    # Update weights
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2

Fully connected

### Verify the Predictions
- Obtained a predicted value from our model and compare to origional.

pred = np.maximum(x.dot(w1),0).dot(w2)

print (pred, "\n", y)

y

#We can see that the weights have been updated.
w1

w2

# Relu just removes the negative numbers.
h_relu


import numpy as np

def Gram_Schmidt(A):
    """
    A: Matriz donde cada columna es un vector de la base
    del espacio vectorial.

    Devuelve una matriz donde cada columna es un vector
    de una base ortonormal del espacio vectorial.
    """
    filas, columnas = A.shape

    # Crear matriz de ceros del tamaño de A
    base_ortonormal = np.zeros((filas,columnas), np.float64)

    base_ortonormal[:,0] = np.copy(A[:,0]) / np.linalg.norm(A[:,0])

    # wi = vi - (Σ[(<vi,wj> / <wj,wj>)]wj), j = 1, 2, 3, ..., i-1
    for i in range(1, columnas):
        vi = np.copy(A[:,i] )
        wi = vi
        for j in reversed(range(i)):
            wj = np.copy(base_ortonormal[:,j])
            cociente = np.dot(vi, wj) / np.dot(wj,wj)  # <vi,wj> / <wj,wj>
            wi -= cociente * wj
            # ui = wi / ‖wi‖, i = 1, 2, 3, ..., n
            ui = wi / np.linalg.norm(wi)

        base_ortonormal[:,i] = ui

    base_ortonormal = np.round(base_ortonormal,10)
    return base_ortonormal


import numpy as np
from scipy.stats import norm

class Option:
    def __init__(self, S, K, r, d, sigma, T):
        '''
        Parameters:
        ===========
        S: stock price
        K: strike price
        r: risk-free interest rate
        d: dividend
        sigma: volatility (implied)
        T: time to maturity

        Returns:
        ===========
        Forward price, vanilla European call and put option' prices, cash-or-nothing call and put options' prices,
        zero coupon bond and forward contract.
        '''

        self.S = S
        self.K = K
        self.r = r
        self.d = d
        self.sigma = sigma
        self.T = T

        self.d1 = (np.log(self.S/self.K) + (self.r - self.d + self.sigma**2 / 2) * self.T) / (self.sigma * np.sqrt(self.T))
        self.d2 = self.d1 - self.sigma * np.sqrt(self.T)

    def forward_price(self):
        '''
        output forward price
        '''
        return self.S * np.exp(self.r * self.T)

    def european_call(self):
        '''
        output vanilla European call option's price using Black-Scholes formula
        '''
        return self.S * np.exp(-self.d * self.T) * norm.cdf(self.d1) - self.K * np.exp(-self.r * self.T)*norm.cdf(self.d2)

    def european_put(self):
        '''
        output vanilla European put option's price using Black-Scholes formula
        '''
        return self.K * np.exp(-self.r * self.T)*norm.cdf(-self.d2) - self.S * np.exp(-self.d * self.T)*norm.cdf(-self.d1)

    def binary_call(self, face_value):
        '''
        output cash-or-nothing call option's price
        '''
        return face_value * np.exp(-self.r * self.T) * norm.cdf(self.d2)

    def binary_put(self, face_value):
        '''
        ouput cash-or-nothing put option's price
        '''
        return face_value * np.exp(-self.r * self.T) * norm.cdf(-self.d2)

    def zero_coupon_bond(self, face_value):
        '''
        output zero coupon bond's price based on the face value
        '''
        return face_value * np.exp(-self.r * self.T)

    def forward_contract(self):
        '''
        output forward contract's value
        '''
        return self.S * np.exp(-self.d * self.T) - self.K * np.exp(-self.r * self.T)

    def print_all_values(self, face_value):
        '''
        print prices of all options and zero coupon bond
        '''
        print('Forward price: {}'.format(self.forward_price()))
        print('European call: {}'.format(self.european_call()))
        print('European put: {}'.format(self.european_put()))
        print('Binary call: {}'.format(self.binary_call(face_value = 1)))
        print('Binary put: {}'.format(self.binary_put(face_value = 1)))
        print('Forward contract: {}'.format(self.forward_contract()))


# Author: Daniel Williams
import numpy as np
import matplotlib.pyplot as plt

'''
The code in this file follows the exercises explained in the paper by
Hogg et al. (2010), as a means of familiarising myself with statistical
approaches to data analysis. Exercises 1-3 consider the technique of linear
regression: first to a linear then quadratic fit.
'''

data = np.genfromtxt('hoggtutorial.txt', names=True)

#%% EXERCISE 1

ex1 = data[4:20]
Y = np.array(ex1['y']).reshape((len(ex1), 1))
A = np.c_[ np.ones(len(ex1)), ex1['x']]
C = np.diag(ex1['sigy'])

# Matrix notation of below: (A.T * C^-1 * A)^-1 (A.T * C^-1 * Y)
Xa = np.linalg.inv(np.dot(A.T,(np.dot(np.linalg.inv(C),A))))
Xb = np.dot(A.T,(np.dot(np.linalg.inv(C),Y)))
X = np.dot(Xa,Xb)

c = X[0]
m = X[1]
xfit = np.arange(min(ex1['x']), max(ex1['x']))
yfit = xfit*m + c

plt.figure(1, figsize=(5,5))
plt.errorbar(ex1['x'], ex1['y'],  yerr=ex1['sigy'], fmt='o')
plt.plot(xfit, yfit)
plt.xlim(0,300)
plt.ylim(0,700)

print(X)
#%% EXERCISE 2

ex2 = data
Y = np.array(ex2['y']).reshape((len(ex2), 1))
A = np.c_[ np.ones(len(ex2)), ex2['x']]
C = np.diag(ex2['sigy'])

# Matrix notation of below: (A.T * C^-1 * A)^-1 (A.T * C^-1 * Y)
Xa = np.linalg.inv(np.dot(A.T,(np.dot(np.linalg.inv(C),A))))
Xb = np.dot(A.T,(np.dot(np.linalg.inv(C),Y)))
X = np.dot(Xa,Xb)

c = X[0]
m = X[1]
xfit = np.arange(min(ex2['x']), max(ex2['x']))
yfit = xfit*m + c

plt.figure(2, figsize=(5,5))
plt.errorbar(ex2['x'], ex2['y'],  yerr=ex2['sigy'], fmt='o')
plt.plot(xfit, yfit)
plt.xlim(0,300)
plt.ylim(0,700)

print('m = ' + str(m))
print('c = ' + c)

#%% EXERCISE 3
ex3 = data[4:20]
Y = np.array(ex3['y']).reshape((len(ex3), 1))
A = np.c_[ np.ones(len(ex3)), ex3['x'], (ex3['x'])**2]
C = np.diag(ex3['sigy'])

# Matrix notation of below: (A.T * C^-1 * A)^-1 (A.T * C^-1 * Y)
Xa = np.linalg.inv(np.dot(A.T,(np.dot(np.linalg.inv(C),A))))
Xb = np.dot(A.T,(np.dot(np.linalg.inv(C),Y)))
X = np.dot(Xa,Xb)

c = X[0]
m = X[1]
q = X[2]

xfit = np.arange(min(ex3['x']), max(ex3['x']))
yfit = q*xfit**2 + xfit*m + c

plt.figure(3, figsize=(5,5))
plt.errorbar(ex3['x'], ex3['y'],  yerr=ex3['sigy'], fmt='o')
plt.plot(xfit, yfit)
plt.xlim(0,300)
plt.ylim(0,700)

print(X)


import numpy as np
# set the matplotlib backend so figures can be saved in the background
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

def feature_normalize(X):
    X_norm = X
    mu = np.zeros((1, X.shape[1]))
    sigma = np.zeros((1, X.shape[1]))
    mu = np.mean(X, axis=0)
    sigma = np.std(X, axis=0)
    X_norm = np.subtract(X, mu) / sigma
    return X_norm, mu, sigma

def gradient_descent_multi(X, y, theta, alpha, num_iters):
    m = y.size
    J_history = np.zeros((num_iters, 1))

    for i in range(0, num_iters):
        h = X.dot(theta)
        thetaNew = theta - (alpha * 1 / m * ((h - y).T.dot(X)).T)
        theta = thetaNew

        # Save the cost J in every iteration
        J_history[i] = compute_cost_multi(X, y, theta)
    return theta, J_history

def compute_cost_multi(X, y, theta):
    m = y.size
    J = 0
    h = X.dot(theta)
    J = 1 / (2 * m) * (h - y).T.dot(h - y)
    return J

def normal_equation(X, y):
    theta = np.zeros((X.shape[1], 1))
    theta = np.linalg.pinv(X.T.dot(X)).dot(X.T).dot(y)
    return theta

data = np.loadtxt("../ex1data2.txt", delimiter=',')
X = data[:, :-1]
y = data[:, -1:]
m = y.size

# % Print out some data points
print('First 10 examples from the dataset: \n')
print(f'x = {X[:10, :]}, \n y = {y[:10]}')

# % Scale features and set them to zero mean
print('Normalizing Features ...\n')

X, mu, sigma = feature_normalize(X)

# % Add intercept term to X
X = np.append(X, np.ones((m, 1)), axis=1)

# % % == == == == == == == == Part 2: Gradient Descent == == == == == == == ==
print('Running gradient descent ...\n')

# % Choose some alpha value
alpha = 0.001
num_iters = 40000

# % Init Theta and Run Gradient Descent
theta = np.zeros((X.shape[1], 1))

theta, J_history = gradient_descent_multi(X, y, theta, alpha, num_iters)

# plot the training loss and accuracy
print("[INFO] about to plot...")
plt.style.use("ggplot")
plt.figure()
plt.plot(np.arange(0, num_iters), J_history, label="error")
plt.title("Training Loss on Gradient Descent")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend(loc="lower left")
plt.savefig('plot.png')

# % Display gradient descent's result
print('Theta computed from gradient descent: \n')
print(f' {theta} \n')
print('\n')

# % Estimate the price of a 1650 sq - ft, 3 br house
sample_house_X = np.array([1650, 3])
s = (sample_house_X - mu) / sigma
s = np.append(s, np.ones((1, 1)))
price = s.dot(theta)[0]

print(
    f'Predicted price of a 1650 sq-ft, 3 br house (using gradient descent):\n ${price}\n')

# % % == == == == == == == == Part 3: Normal Equations == == == == == == == ==

print('Solving with normal equations...\n')

data = np.loadtxt("../ex1data2.txt", delimiter=',')
X = data[:, :-1]
y = data[:, -1:]
m = y.size

# % Add intercept term to X
X = np.append(X, np.ones((m, 1)), axis=1)

theta = normal_equation(X, y)

# % Display normal equation's result
print('Theta computed from the normal equations: \n')
print(f' {theta} \n')
print('\n')

# % Estimate the price of a 1650 sq - ft, 3 br house
s = np.array([1650, 3, 1])
price = s.dot(theta)[0]

print(
    f'Predicted price of a 1650 sq-ft, 3 br house (using normal equation):\n ${price}\n')


# Solutions to problem 1
import scipy as sp
import numpy as sp
from matplotlib import pyplot as plt
from scipy.signal import fftconvolve

def getFrame(m):
    coeffs = []
    k_max = int(sp.pi*2**(m+1))
    for k in xrange(k_max):
        coeffs.append(-2**m*(sp.cos((k+1)*2**(-m)) - sp.cos(k*2**(-m))))
    coeffs.append(-2**m*(1-sp.cos(k_max*2**(-m))))
    return sp.array(coeffs)

# frame_4 = getFrame(4)
# frame_6 = getFrame(6)
# frame_8 = getFrame(8)

# Here's how to plot each one:
# plt.plot([x*2*sp.pi/len(frame_6) for x in range(len(frame_6))],frame_6,drawstyle='steps')

# Solutions to problem 2

def getDetail(m):
    coeffs = []
    k_max = int(sp.pi*2**(m+1))
    for k in xrange(k_max):
        coeffs.append(-2**m*(2*sp.cos((2*k+1)*2**(-m-1)) - sp.cos((k+1)*2**(-m)) - sp.cos(k*2**(-m))))
    if (2*sp.pi < (2*k_max+1)*2**(-m-1)):
        coeffs.append(-2**m*(1-sp.cos(k_max*2**(-m))))
    else:
        coeffs.append(-2**m*(2*sp.cos((2*k_max+1)*2**(-m-1)) - 1 - sp.cos(k*2**(-m))))
    return sp.array(coeffs)

# detail = getDetail(4)

# Here's how to plot the details
#b = []
#for i in detail:
#    b.extend([i,-i])
#plt.plot([x*2*sp.pi/len(b) for x in range(len(b))],b,drawstyle='steps')

# Here's how to calculate the frame for m=5
#details = getDetail(4)
#frame = getFrame(4)
#frame_5 = sp.zeros(2*len(details))
#for i in range(2*len(details)):
#    frame_5[i] = frame[i/2] + (-1)**i*details[i/2]

''' Notes: these methods use the fftconvolve function from sp.signal.
	I have found that the default, mode='full', is the best setting.
	However, the convolution outputs a vector that is one entry too
	long, and so either the first or last element should be omitted.
'''

def dwt(f, lo, hi):
    '''
    Compute the discrete wavelet transform of f with respect to
    the wavelet filters lo and hi.
    Inputs:
        f -- numpy array corresponding to the signal
        lo -- numpy array giving the lo-pass filter
        hi -- numpy array giving the hi-pass filter
    Returns:
        list of the form [A, D1, D2, ..., Dn] where each entry
        is a numpy array. These are the approximation frame (A)
        and the detail coefficients.
    '''
    ans = []
    frame = f
    while len(frame) >= len(lo):
        detail = fftconvolve(frame, hi, mode='full')[1:][::2]
        frame = fftconvolve(frame, lo, mode='full')[1:][::2]
        ans.append(detail)
    ans.append(frame)
    ans.reverse()
    return ans

def idwt(t, lo, hi):
    '''
    Compute the inverse discrete wavelet transform of a list of
    transform coefficients with respect to the wavelet filters
    lo and hi.
    Inputs:
        t -- a list containing the frame and detail coefficients of
             a signal, corresponding to the output of dwt.
        lo -- numpy array giving the lo-pass filter
        hi -- numpy array giving the hi-pass filter
    Outputs:
        f -- a numpy array giving the recovered signal.
    '''
    f = t[0]
    for i in xrange(len(t)-1):
        det = t[i+1]
        frame = np.zeros(len(f)*2)
        frame[::2] = f
        frame = fftconvolve(frame, lo, mode='full')[:-1]
        detail = np.zeros(len(det)*2)
        detail[::2] = det
        detail = fftconvolve(detail, hi, mode='full')[:-1]
        f = detail + frame
    return f


import numpy as np
from numpy.random import *
import matplotlib.pyplot as plt
from numpy.fft import *

""" Ce script genere des interferogrammes tels qu'obtenus avec un interferometre
de Michelson dans le but d'etudier la transformée de Fourier et de comprendre
comment la resolution spectrale est déterminée.
"""

def readVectorsFromFile(filename):
	x = np.loadtxt(filename, usecols=(0))
	y = np.loadtxt(filename, usecols=(1))
	return (x,y)

def generateHeNeInterferogram(xMin, xMax, N):
	""" Genere un tableau de N valeurs equidistantes enntre xMin et xMax.
	Ensuite, genere un tableau de N valeurs qui representent un interferogramme
	d'un laser He-Ne a 0.6328 microns. On ajoute du bruit pour rendre le tout
	plus realiste.
	"""
	dx = (xMax - xMin)/N
	x = np.linspace(xMin, xMax, N)
	noise = random(len(x))*0.05
	y = 1+np.cos(2 * np.pi / 0.6328 * x)+noise
	return (x,y)

def generateWhiteLightInterferogram(xMin, xMax, N):
	""" Genere un tableau de N valeurs equidistantes enntre xMin et xMax.
	Ensuite, genere un tableau de N valeurs qui representent un interferogramme
	d'une source blanche visible. On ajoute du bruit pour rendre le tout
	plus realiste.
	"""
	dx = (xMax - xMin)/N
	x = np.linspace(xMin, xMax, N)
	noise = random(len(x))*0.05
	k1 = 1/0.4
	k2 = 1/0.8
	y = (1+(np.sin(2 * np.pi * (k1+k2)*x/2)/x))**2
	return (x,y)

def fourierTransformInterferogram(x,y):
	""" A partir du tableau de valeurs Y correspondant a l'abscisse X,
	la transformée de Fourier est calculée et l'axes des fréquences (f en
	µm^-1) et des wavelengths (1/f en microns) est retournée.

	Le spectre est un ensemble de valeurs complexes pour lesquelles l'amplitude
	et la phase sont pertinentes: l'ordre des valeurs commence par la valeur DC (0)
	et monte jusqu'a f_max=1/2/∆x par resolution de ∆f = 1/N/∆x. A partir de la
	(N/2) ieme valeur, la frequence est negative jusqu'a -∆f dans la N-1 case.
	Voir
	https://github.com/dccote/Enseignement/blob/master/HOWTO/HOWTO-Transformes%20de%20Fourier%20discretes.pdf
	"""
	spectrum = fft(y)
	dx = x[1]-x[0] # on obtient dx, on suppose equidistant
	N = len(x)     # on obtient N directement des données
	frequencies = fftfreq(N, dx) # Cette fonction est fournie par numpy
	wavelengths = 1/frequencies  # Les fréquences en µm^-1 sont moins utiles que lambda en µm
	return (wavelengths, frequencies, spectrum)

def plotCombinedFigures(x, y, w, s, title="", left=400, right=800):
	""""
	On met l'interferogramme et le spectre sur la meme page.
	"""
	fig, (axes, axesFFT) = plt.subplots(2,1,figsize=(10, 7))
	axes.plot(x, y, '-')
	axes.set_title("Interferogramme")
	axesFFT.plot(w*1000, abs(s), 'o-')
	axesFFT.set_xlim(left=left, right=right)
	axesFFT.set_xlabel("Longueur d'onde [nm]")
	axesFFT.set_title(title)
	plt.show()

# # Basse resolution
# (x,y) = generateHeNeInterferogram(xMin=-10, xMax=10, N=200) # en microns
# (w, f, s)  = fourierTransformInterferogram(x,y)
# df = f[1]-f[0]
# dl = 0.6328*0.6328*df*1000 # x 1000 pour nm
# plotCombinedFigures(x,y,w,s,left=632.8-5*dl, right=632.8+5*dl, title="Spectre He-Ne basse resolution {0:0.2f} nm".format(dl))

# # Haute resolution
# # Resolution ∆f = 1/(200 µm * 2000)
# # Resolution @ 632.8 nm : ∆lambda = 632.8^2 * ∆f car ∆lambda/lambda = ∆f/f.
# (x,y) = generateHeNeInterferogram(xMin=-100, xMax=100, N=2000) # en microns
# (w, f, s) = fourierTransformInterferogram(x,y)
# df = f[1]-f[0]
# dl = 0.6328*0.6328*df*1000
# plotCombinedFigures(x,y,w,s,left=632.8-5*dl, right=632.8+5*dl, title="Spectre He-Ne haute resolution {0:0.2f} nm".format(dl))

# # Tres haute resolution
# # Resolution ∆f = 1/(2000 µm * 2000)
# # Resolution @ 632.8 nm : ∆lambda = 632.8^2 * ∆f
# (x,y) = generateHeNeInterferogram(xMin=-1000, xMax=1000, N=20000) # en microns
# (w, f, s) = fourierTransformInterferogram(x,y)
# df = f[1]-f[0]
# dl = 0.6328*0.6328*df*1000
# plotCombinedFigures(x,y,w,s,left=632.8-5*dl, right=632.8+5*dl, title="Spectre He-Ne tres haute resolution {0:0.2f} nm".format(dl))

# # Hyper haute resolution
# # Resolution ∆f = 1/(20000 µm * 20000)
# # Resolution @ 632.8 nm : ∆lambda = 632.8^2 * ∆f
# (x,y) = generateHeNeInterferogram(xMin=-10000, xMax=10000, N=200000) # en microns
# (w, f, s) = fourierTransformInterferogram(x,y)
# df = f[1]-f[0]
# dl = 0.6328*0.6328*df*1000
# plotCombinedFigures(x,y,w,s,left=632.8-5*dl, right=632.8+5*dl, title="Spectre He-Ne hyper haute resolution {0:0.2f} nm".format(dl))

# Spectre de lumiere blanche
# Resolution ∆f = 1/(20000 µm * 20000)
# Resolution @ 500 nm : ∆lambda = 500^2 * ∆f
(x,y) = generateWhiteLightInterferogram(xMin=-100, xMax=100, N=20000) # en microns
(w, f, s)  = fourierTransformInterferogram(x,y)
df = f[1]-f[0]
dl = 0.500*0.500*df*1000 # resolution autour de 0.500 µm en nm
plotCombinedFigures(x,y,w,s,left=0, right=2000, title="Spectre lumiere blanche, resolution {0:0.2f} nm".format(dl))


# integrate the equations of motion of a pendulum, w/o the small angle
# approximation

import numpy
import pylab
import math

# global parameters
g = 9.81     # gravitational acceleration [m/s]
L = 9.81     # length of pendulum [m]

class pendulumHistory:
    """ simple container to store the pendulum history """

    def __init__(self):
        self.t = None
        self.theta = None
        self.omega = None

    def energy(self):
        """ return the energy (per unit mass) """
        return 0.5*L**2*self.omega**2 - g*L*numpy.cos(self.theta)

def rhs(theta, omega):
    """ equations of motion for a pendulum
        dtheta/dt = omega
        domega/dt = - (g/L) sin theta """

    return omega, -(g/L)*numpy.sin(theta)

def intVVerlet(theta0, dt, tmax, rhs):
    """ integrate the equations of motion using Euler-Cromer """

    # initial conditions
    t = 0.0
    theta = theta0
    omega = 0.0    # at the maximum angle, the angular velocity is 0

    # store the history for plotting
    tPoints = [t]
    thetaPoints = [theta]
    omegaPoints = [omega]

    while (t < tmax):

        # get the RHS at time-level n
        thetadot, omegadot = rhs(theta, omega)

        thetanew = theta + dt*thetadot + 0.5*dt**2*omegadot

        # get the RHS with the updated theta -- omega doesn't matter
        # here, since we only need thetadot and omega doesn't affect
        # that.
        thetadot_np1, omegadot_np1 = rhs(thetanew, omega)

        omeganew = omega + 0.5*dt*(omegadot + omegadot_np1)

        t += dt

        # store
        tPoints.append(t)
        thetaPoints.append(thetanew)
        omegaPoints.append(omeganew)

        # set for the next step
        theta = thetanew; omega = omeganew

    # return a pendulumHistory object with the trajectory
    H = pendulumHistory()
    H.t = numpy.array(tPoints)
    H.theta = numpy.array(thetaPoints)
    H.omega = numpy.array(omegaPoints)

    return H

# 10 degree pendulum
theta0 = numpy.radians(10.0)

# period estimate
T = 2.0*math.pi*math.sqrt(L/g)*(1.0 + theta0**2/16.0)
print "period = ", T

tmax = 10.0*T
#dts = [0.5, 0.25, 0.125, 0.06125, 0.030625]
dts = [1, 0.1, 0.01, 0.001, 0.0001]

err = []
for dt in dts:
    HVVerlet = intVVerlet(theta0, dt, tmax, rhs)

    E = HVVerlet.energy()
    err.append(abs(E[-1]-E[0])/abs(E[0]))

    print dt, abs(E[-1]-E[0])/abs(E[-1])
    pylab.plot(HVVerlet.t, HVVerlet.theta, label="dt = %f" % (dt))

leg = pylab.legend(frameon=False)
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')

pylab.savefig("Verlet-theta-dt.png")

pylab.clf()

pylab.scatter(dts, err)

pylab.plot(numpy.array(dts), err[0]*(dts[0]/numpy.array(dts))**-2, label="2nd order scaling")
pylab.plot(numpy.array(dts), err[0]*(dts[0]/numpy.array(dts))**-4, ls=":", label="4th order scaling")
pylab.plot(numpy.array(dts), err[0]*(dts[0]/numpy.array(dts))**-6, ls="--", label="6th order scaling")

leg = pylab.legend(loc=2, frameon=False)
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')

ax = pylab.gca()
ax.set_xscale('log')
ax.set_yscale('log')

pylab.xlim(0.00001, 1.0)
pylab.ylim(1.e-12, 1.e-1)

pylab.savefig("Verlet-E-converge.png")


# 数学函数
# ```
# 本次学习内容为数学函数，顾名思义，就是常见的数学上的操作，最基础的有：加减乘除等
# 运算符为 元素级，只用于位置相同的元素之间，所得到的运算结果组成一个
# 新的数组
# ```
## 基础的数学操作
# - 加法
#   ```
#   numpy.add(x1, x2, *args, **kwargs)
#   ```
# - 减法
#   ```
#   numpy.subtract(x1, x2, *args, **kwargs)
#   ```
# - 乘法
#   ```
#   numpy.multiply(x1, x2, *args, **kwargs)
#   ```
# - 除法
#   ```
#   numpy.divide(x1, x2, *args, **kwargs)
#   ```
# - 除法，只保留整数
#   ```
#   numpy.floor_divide(x1, x2, *args, **kwargs)
#   ```
# - N次方
#   ```
#   numpy.power(x1, x2, *args, **kwargs)
#   ```
import numpy as np

x = np.arange(25,37).reshape([3,4])
print(x)

# y = x + 1
print(np.add(x,1))
print(np.subtract(x, 1))
print(np.multiply(x, 2))
print(np.divide(x, 2))
print(np.floor_divide(x, 3))
print(np.power(x, 2))

y = np.arange(1,13).reshape([3,4])

print(np.add(x, y))
print(np.subtract(x, y))
print(np.multiply(x, y))

# - 平方根
#   ```
#   numpy.sqrt(x1, x2, *args, **kwargs)
#   ```
print(np.sqrt(x))
# - 平方
#   ```
#   numpy.square(x1, x2, *args, **kwargs)
#   ```

print(np.square(x)) # == print(np.power(x,2))

print(np.power(y, 3))

# 三角函数
#   ```
#   几何图形三角函数，常用的：求正弦值，余弦值
#   ```

## 三角函数操作
# - 正弦值
#   ```
#   numpy.sin(x1, x2, *args, **kwargs)
#   ```
#   ```
#   numpy.cos(x1, x2, *args, **kwargs)
#   ```
#   ```
#   numpy.tan(x1, x2, *args, **kwargs)
#   ```
#   ```
#   numpy.arcsin(x1, x2, *args, **kwargs)
#   ```
#   ```
#   numpy.arccos(x1, x2, *args, **kwargs)
#   ```
#   ```
#   numpy.arctan(x1, x2, *args, **kwargs)
#   ```
# 要点 2
# 通用函数（universal function）通常叫作ufunc，它对数组中的各个元素逐一进行操作。这表明，通用函数分别处理输入数组的每个元素，生成
# 的结果组成一个新的输出数组。输出数组的大小跟输入数组相同
# 很多数学运算符合通用函数的定义，例如，计算平方根的sqrt() 函数、用来取对数的log() 函数和求正弦值的sin() 函数

l  = np.linspace(start=0, stop=np.pi / 2, num=10)

print(l)

# 要点3
# 通过不同的 axis ，numpy 会沿着不同的方向进行操作：如果不设置，那么对所有的元素操作；如果axis=0 ，则沿着纵轴进行操
# 作； axis=1 ，则沿着横轴进行操作。但这只是简单的二位数组，如果是多维的呢？可以总结为一句话：设axis=i ，则 numpy 沿着第i 个
# 下标变化的方向进行操作。

# 要点 4
# 聚合函数 是指对一组值（比如一个数组）进行操作，返回一个单一值作为结果的函数。因而，求数组所有元素之和的函数就是聚合函
# 数。ndarray 类实现了多个这样的函数。

# 要点 5
# 上限 基本上是 +1
# 下限 是只取整数

# numpy.clip
# 裁剪 等于把数组里小于裁剪下限的值换成下限值，大于上限的值换成上限值


import numpy as np
import matplotlib.pyplot as plt

def simular_gp(mu, sigma, m, ax=None):
  X = np.random.multivariate_normal(mu, cov=sigma, size=m)
  d = mu.shape[0]

  if ax is None:
    fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15,5))

  for i in range(m):
    ax.plot(X[i,:])
  ax.set_xlabel("T")
  ax.set_ylabel("$X_i$")
  ax.set_xticks([i for i in range(d)])

def m_zeros(X, parametros):
  return np.zeros(X.shape)

def m_constante(X, parametros):
  c = parametros[0]
  return np.repeat(c, X.shape[0]).reshape(X.shape)

def m_linear(X, parametros):
  a, b = parametros
  return np.linspace(a,b, X.shape[0]).reshape(X.shape)

def Cov(x1, x2, kernel, parametros):
  n1 = x1.shape[0]
  n2 = x2.shape[0]
  cov = np.zeros((n1,n2))
  for i in range(n1):
    cov[i] = kernel(x1[i], x2, parametros)
  return cov

def plotar_cov(cov, m):
  fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(15,5))
  d = cov.shape[0]
  map = ax[0].matshow(cov)
  ax[0].set_xlabel("$X_1$")
  ax[0].set_ylabel("$X_2$")
  ax[0].set_title("$\Sigma$")
  fig.colorbar(map, ax=ax[0])
  simular_gp(m_zeros(x1, []).flatten(), cov, m, ax=ax[1])
  ax[1].set_title("Amostras do $\mathcal{GP}$")
  plt.tight_layout()

def plotar_kernel(x1, x2, kernel, parametros, mu=m_zeros, mu_parametros=[], m=5):
  cov = Cov(x1, x2, kernel, parametros)
  plotar_cov(cov, m)

def k_linear(x1, x2, parametros):
  return x1.dot(x2.T)

def k_polinomial(x1, x2, parametros):
  return x1.dot(x2.T) ** parametros[0]

def k_rbf(x1, x2, parametros):
  sigma = parametros[0]
  return np.exp(-.5*sigma**2 * np.sqrt(np.sum((x1 - x2)**2, axis=1))).flat

def k_laplaciano(x1, x2, parametros):
  alpha = np.array(parametros[0])
  return np.exp(-alpha * np.abs(x1 - x2)).flat

def sin2(x):
  return (1 - np.cos(2*x))/2

def k_periodico(x1, x2, parametros):
  p = np.array(parametros[0])
  sigma = np.array(parametros[1])
  l = np.array(parametros[2])
  return sigma*np.exp(-(2 * sin2((np.pi * np.abs(x1 - x2))/p)/l)).flat

from numpy import linalg

def GP(Tc, Xc, Td, mu, kernel, mu_parametros=[], kernel_parametros=[], ruido=0):
  Scc = Cov(Tc, Tc, kernel, kernel_parametros) + np.eye(Xc.shape[0])*ruido
  Sdd = Cov(Td, Td, kernel, kernel_parametros)
  Scd = Cov(Tc, Td, kernel, kernel_parametros)
  Sdc = Scd.T
  Xd = mu(Td, mu_parametros) + Sdc.dot( linalg.inv(Scc)).dot(Xc - mu(Tc, mu_parametros))
  Sd = Sdd - Sdc.dot(linalg.inv(Scc)).dot(Scd)
  return Xd, Sd


import numpy as np
import scipy.stats as stats

class MultiArmedBandit:
    """Define a simple implementation of Multi-Armed Bandit with a Beta distribution.

    Based on https://peterroelants.github.io/posts/multi-armed-bandit-implementation/
    """

    def __init__(self, n_bandits, reshape_factor=1, minimize=False, boltzmann=False):
        self.n_bandits      = n_bandits
        self.alpha_priors   = np.ones(n_bandits)
        self.beta_priors    = np.ones(n_bandits)
        self.reshape_factor = reshape_factor # Higher reshape factor favors exploitation
        self.minimize       = minimize
        self.boltzmann      = boltzmann

    def choose_bandit(self):

        if self.boltzmann:
            probs = self.get_empirical_probs()
            self.chosen_bandit_idx = np.random.choice(self.n_bandits, p=np.exp(probs) / np.sum(np.exp(probs)))

        else:

            # Define prior distribution for each bandit
            self.bandit_priors = [stats.beta(alpha, beta)
                                  for alpha, beta in zip(self.alpha_priors, self.beta_priors)]

            # Sample a probability theta for each bandit
            theta_samples = [d.rvs(1) for d in self.bandit_priors]

            # Choose a bandit
            if self.minimize:
                self.chosen_bandit_idx = np.argmin(theta_samples)
            else:
                self.chosen_bandit_idx = np.argmax(theta_samples)

        return self.chosen_bandit_idx

    def update_posterior(self, observation):
        """Update the posterior of the current chosen bandit based on the observation (0 or 1)
        """

        assert 0 <= observation <= 1

        # Update posterior

        self.alpha_priors[self.chosen_bandit_idx] += observation * self.reshape_factor
        self.beta_priors[self.chosen_bandit_idx] += (1 - observation) * self.reshape_factor

    def get_empirical_probs(self):
        return np.array([(a / (a + b - 1)) for a, b in zip(self.alpha_priors, self.beta_priors)])

if __name__ == '__main__':

    import matplotlib.pyplot as plt
    import seaborn as sns

    sns.set_style('darkgrid')

    nb_bandits = 4

    # True probability of winning for each bandit
    p_bandits = np.random.rand(nb_bandits)

    def pull(i):
        if np.random.rand() < p_bandits[i]:
            return 1
        else:
            return 0

    def plot(priors, step, ax):
        """Plot the priors for the current step."""
        plot_x = np.linspace(0.001, .999, 100)
        for prior in priors:
            y = prior.pdf(plot_x)
            p = ax.plot(plot_x, y)
            ax.fill_between(plot_x, y, 0, alpha=0.2)
        ax.set_xlim([0, 1])
        ax.set_ylim(bottom=0)
        ax.set_title('Priors at step {}'.format(step))

    mab = MultiArmedBandit(nb_bandits, 1)

    plots = [1, 2, 5, 10, 25, 50, 100, 200, 500, 1000]
    # plots = np.logspace(0.2, 2, 10).astype(int)

    # Setup plot
    fig, axs = plt.subplots(5, 2, figsize=(8, 10))
    axs = axs.flat

    trials = [0] * nb_bandits

    n = 1000
    # Run the trail for `n` steps
    for step in range(1, n + 1):
        idx = mab.choose_bandit()

        trials[idx] += 1

        if step in plots:
            plot(mab.bandit_priors, step, next(axs))

        x = pull(idx)

        mab.update_posterior(x)

    emperical_p_bandits = [(a / (a + b - 1)) for a, b in zip(mab.alpha_priors, mab.beta_priors)]
    for i in range(nb_bandits):
        print(('True prob={:.2f};  '
               'Emperical prob={:.2f};  '
               'Trials={:d}'.format(p_bandits[i], emperical_p_bandits[i], trials[i])
               ))

    plt.tight_layout()
    plt.show()


#Ref: Sreenivas Sarwar Anik

"""

Hough Transform to detect straight lines and measure angles between them.

https://en.wikipedia.org/wiki/Hough_transform

The origin is the top left corner of the original image. X and Y axis are
horizontal and vertical edges respectively. The distance is the minimal
algebraic distance from the origin to the detected line. The angle accuracy
can be improved by decreasing the step size in the theta array.

https://scikit-image.org/docs/dev/auto_examples/edges/plot_line_hough_transform.html#sphx-glr-auto-examples-edges-plot-line-hough-transform-py

Image downloaded from: https://geometryhelp.net/wp-content/uploads/2019/04/intersecting-lines.jpg
Then inverted to dark background.
"""

from skimage.transform import (hough_line, hough_line_peaks)
import numpy as np
import cv2
from matplotlib import pyplot as plt

image = cv2.imread('images/lines1.jpg', 0) #Fails if uses as-is due to bright background.
#Also try lines2 to see how it only picks up straight lines
#Invert images to show black background
image = ~image  #Invert the image (only if it had bright background that can confuse hough)
plt.imshow(image, cmap='gray')

# Set a precision of 1 degree. (Divide into 180 data points)
# You can increase the number of points if needed.
tested_angles = np.linspace(-np.pi / 2, np.pi / 2, 180)

# Perform Hough Transformation to change x, y, to h, theta, dist space.
hspace, theta, dist = hough_line(image, tested_angles)

plt.figure(figsize=(10,10))
plt.imshow(hspace)

#Now, to find the location of peaks in the hough space we can use hough_line_peaks
h, q, d = hough_line_peaks(hspace, theta, dist)

#################################################################
#Example code from skimage documentation to plot the detected lines
angle_list=[]  #Create an empty list to capture all angles

# Generating figure 1
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
ax = axes.ravel()

ax[0].imshow(image, cmap='gray')
ax[0].set_title('Input image')
ax[0].set_axis_off()

ax[1].imshow(np.log(1 + hspace),
             extent=[np.rad2deg(theta[-1]), np.rad2deg(theta[0]), dist[-1], dist[0]],
             cmap='gray', aspect=1/1.5)
ax[1].set_title('Hough transform')
ax[1].set_xlabel('Angles (degrees)')
ax[1].set_ylabel('Distance (pixels)')
ax[1].axis('image')

ax[2].imshow(image, cmap='gray')

origin = np.array((0, image.shape[1]))

for _, angle, dist in zip(*hough_line_peaks(hspace, theta, dist)):
    angle_list.append(angle) #Not for plotting but later calculation of angles
    y0, y1 = (dist - origin * np.cos(angle)) / np.sin(angle)
    ax[2].plot(origin, (y0, y1), '-r')
ax[2].set_xlim(origin)
ax[2].set_ylim((image.shape[0], 0))
ax[2].set_axis_off()
ax[2].set_title('Detected lines')

plt.tight_layout()
plt.show()

###############################################################
# Convert angles from radians to degrees (1 rad = 180/pi degrees)
angles = [a*180/np.pi for a in angle_list]

# Compute difference between the two lines
angle_difference = np.max(angles) - np.min(angles)
print(180 - angle_difference)   #Subtracting from 180 to show it as the small angle between two lines


import numpy as np
import timeit
import time
from scipy import integrate

def golub_welsch(n):
    i = np.arange(n)
    b = (i+1) / np.sqrt(4*(i+1)**2 - 1)
    J = np.diag(b, -1) + np.diag(b, 1)
    x, ev = np.linalg.eigh(J)   #x are eigenvalues
    w = 2 * ev[0,:]**2
    return x, w

def gauss(f, a, b, N):
    x,w = golub_welsch(N)
    x_transformed = 0.5*(a+b+(b-a)*x)
    values = w*f(x_transformed)
    return (b-a)/2.0 * np.sum(values)

def composite_gauss(f, a, b, n, N):     #n number of subintervals, N degree
    h = (b-a)/n
    intervals = np.linspace(a, b, n)
    x,w = golub_welsch(N)
    x_transformed = 0.5*(2*a+h+h*x)
    result = 0

    for i in range(n):
        result += h/2.0*np.sum(w*f(x_transformed))
        x_transformed += h

    return result

def mpr(f, a, b, N):
    h = float(b-a)/N
    x = np.linspace(a+h/2, b-h/2, N)
    ft = h*f(x)
    return np.sum(ft)

def tpr(f, a, b, N):
    h = float(b-a)/N
    x = np.linspace(a, b, N+1)
    return h*np.sum(f(x[1:-1]))+h/2*(f(x[0])+f(x[N]))

def simpson(f, a, b, N):
    N*=2
    h = float(b-a)/N
    x = np.linspace(a, b, N+1)
    fx = f(x)
    a = 2*(h/3)*np.sum(fx[2:-1:2])
    b = 2*(h/6)*(fx[0]+fx[-1])
    c = 8*(h/6)*np.sum(fx[1::2])
    return a+b+c

def adaptive_h(f, a, b, tol=0.00000001):                                 #Usually not efficient because of recursion. TODO: Implement in Cython
    return _adaptive_h(f,a,b,f(a),f(b), f((a+b)/2), (b-a)/6*(f(a)+4*f((a+b)/2) +f(b)),tol)

def _adaptive_h(f, a, b, fa, fb, fm, sab, tol):
    h = (b-a)/2
    fm_new1 = f(a+h/2)
    fm_new2 = f(b-h/2)
    sam = h/6*(fa+4*fm_new1+fm)
    smb = h/6*(fm+4*fm_new2+fb)

    err = np.abs(sam+smb-sab)

    if(err>15*tol):
        return _adaptive_h(f,a,a+h,fa,fm,fm_new1,sam,tol/2)+_adaptive_h(f,a+h,b,fm,fb,fm_new2,smb,tol/2)

    return sam+smb

def sliced(f, y):
    def g(*args):
        return f(*args, y)

    return g

def sliced_list(lf, y):
    return [sliced(f, y) for f in lf]

def quad_ruleNd(rule, n):
    """
    Generates a high dimensional quad rule from 1-dimensional blue-print

    Arguments:

    rule: A quadrature rule which expects:

        - g: a function expecting 1 argument, returning a float
        - a: float, lower bounds
        - b: float, upper bounds
        - N: resolution

    n:    Dimensions, a positive integer

    Returns a quadrature rule which expects:

        - f: A function with n float arguments, returns a float

        - bounds: A list of n functions, which return a 2-tuple of floats. The first function should expect n-1 arguments, the second n-2 etc.

        - nodes: A list of positive integers, representing the resolution along each axis

    Equivalent to:

    nquad(f, [lambda a1, a2, ... an: bounds[0](an, ... a1), ....])
    """

    if(n==1):
        return lambda f, bounds, nodes: rule(f, bounds[0]()[0], bounds[0]()[1], nodes[0])

    lower_rule = quad_ruleNd(rule, n-1)

    def f(f, bounds, nodes):
        integral = lambda y:  lower_rule(sliced(f, y), sliced_list(bounds[:-1], y), nodes[:-1])
        return rule(np.vectorize(integral), bounds[-1]()[0], bounds[-1]()[1], nodes[-1])

    return f

def cube_quad_ruleNd(rule, n):
    qnd = quad_ruleNd(rule, n)
    return lambda f, bounds, nodes: qnd(f, [lambda *args: bound for bound in bounds], nodes)

def test(f, exact_val, name):
    result = f()
    t = timeit.timeit(f, number = 30)
    print(name, ":", result, ", ERROR: ", np.abs(exact_val-result), ", TOOK:", t, "seconds")

def test_batch(f, exact):
    test(lambda: simpson(f, 0, 4, 12000),exact,"Simpson")
    test(lambda: adaptive_h(f, 0, 4),exact,"Adaptive")
    test(lambda: gauss(f, 0, 4, 80),exact,"Gauss")
    test(lambda: tpr(f, 0, 4, 120000),exact,"TPR")
    test(lambda: mpr(f, 0, 4, 120000),exact,"MPR")
    print()

def expensive(x):
    if(x<2.3234):
        return 0.0
    return x-2.3234

if __name__ == "__main__":
    f = lambda x: 1/(1+25*x**2)
    exact = integrate.quad(f, 0, 4)[0]

    test_batch(f, exact)

    f = lambda x: x**0.5
    exact = integrate.quad(f, 0, 4)[0]

    test_batch(f, exact)

    f = lambda x: np.sin(x)
    exact = integrate.quad(f, 0, 4)[0]

    test_batch(f, exact)

    exact = integrate.quad(np.vectorize(expensive), 0, 4)[0]

    test_batch(np.vectorize(expensive), exact)


#!/usr/bin/env python3

"""

Image Processing -- SCC0251/5830— Prof. Moacir A. Ponti

Assignment 1 : image generation

assignment1: fundamentals of image processing -- generation, sampling, and quantization

                2021, April -- 1º Semester

"""

__author__      = "Erikson Aguiar -- NUSP: 11023222"

import numpy as np
import random
'''
    normalize values of pixels

    parameters: z is an image (numpy array), c is min value (int) and d is max value
    return: image normalized from min to max values of pixels
'''
def normalize(z, c, d):
    a = z.min()
    b = z.max()

    tz = (z-a)*((d-c)/(b-a)) + c

    return tz

'''
    generate synthetic image using function one

    parameters: C is the image size (int)
    return: synthetic image
'''
def gen_func_one(c):
    new_image = np.zeros((c,c), dtype=float)
    for x in range(c):
        for y in range(c):
            new_image[x,y] = ((x*y) + (2*y))

    n_img = normalize(new_image, 0, ((2**16)-1))

    return n_img

'''
    generate synthetic image using function two

    parameters: C is the image size (int), Q is parameter for image generation (int)
    return: synthetic image
'''
def gen_func_two(c,q):
    new_image = np.zeros((c,c), dtype=float)
    for x in range(c):
        for y in range(c):
            new_image[x,y] = np.abs(np.cos(x/q) + (2*np.sin(y/q)))

    n_img = normalize(new_image, 0, ((2**16)-1))

    return n_img

'''
    generate synthetic image using function three

    parameters: C is the image size (int), Q is parameter for image generation (int)
    return: synthetic image
'''
def gen_func_three(c,q):
    new_image = np.zeros((c,c), dtype=float)
    for x in range(c):
        for y in range(c):
            new_image[x,y] = float(np.abs((3*(x/q)) - (np.cbrt(y/q))))

    n_img = normalize(new_image, 0, ((2**16)-1))

    return n_img

'''
    generate synthetic image using function four

    parameters: C is the image size (int), S is a seed value for random number generation (int)
    return: synthetic image
'''
def gen_func_four(c,s):
    random.seed(s)
    new_image = np.zeros((c,c), dtype=float)
    for x in range(c):
        for y in range(c):
            new_image[x,y] = random.random()

    n_img = normalize(new_image, 0, ((2**16)-1))

    return n_img

'''
    function to walking on image coordinates

    parameters: C is the image size (int), x coordinate with axis x (int), y is coordinate with axis y (int)
    return: coordinate x (int) and coordinate y (int)
'''
def random_walk(c,x, y):
    dx = random.randint(-1,1)
    dy = random.randint(-1,1)
    new_x = np.mod((x+dx),c)
    new_y = np.mod((y+dy),c)

    return new_x,new_y

'''
    generate synthetic image using function four that represent a random walk

    parameters: C is the image size (int), S is a seed value for random number generation (int)
    return: synthetic image
'''
def gen_func_five(c,s):
    random.seed(s)
    new_image = np.zeros((c,c), dtype=float)
    x, y = 0,0
    new_image[x,y] = 1
    for _ in range(1, (1+c**2)):
        x, y = random_walk(c,x,y)
        new_image[x,y] = 1

    return new_image

'''
    downsampling image CxC following regions r, which select first value of the region
    parameters: image (numpy array), c is image size c*c (int), N is size sampled image (int)
    return: image reduced
'''
def downsampling(image, c,n):
    r = int((c/n)**2)
    tam = int(c/(r/2))
    new_image = np.zeros((tam,tam), dtype=float)
    px, py = 0,0
    for x in range(0, c, int(c/n)):
        for y in range(0, c, int(c/n)):
            px, py = int(x/(c/n)), int(y/(c/n))
            new_image[px,py] = image[x,y]

    return new_image

'''
    Quantize images with B bits
    parameters: image (numpy array), B is number of bitwise(B)
    return image quantized
'''
def quantization(image, B):
    #normalize image
    new_image = normalize(image, 0, 255).astype(np.uint8)
    #new_image = new_image & 0b11100000
    #calculate bitwise
    zeros = 8 - B
    b = '0b'
    b += '1' * B
    b += '0' * zeros
    #apply AND between pixel value and mask of bits
    new_image = new_image & int(b, base=2)

    return new_image

'''
    calculate difference between two images using root square error
    parameters: reference image (numpy array), generation image (numpy array)
    return: root square error (float)
'''
def difference_img(ref_img, gen_img):
    rse = np.sqrt(np.sum((gen_img - ref_img)**2))

    return rse

'''
    function to select method for generate images
    paramters: func is a method (int), C image size C*C (int), Q generation (int)
               S seed for random number generation (int)
    return: return a synthetic image from a method selected
'''
def generate_image(func, C, Q, S):
    synthetic_img = None
    #swich function
    if func == 1:
        synthetic_img = gen_func_one(C)
    elif func == 2:
        synthetic_img = gen_func_two(C,Q)
    elif func == 3:
        synthetic_img = gen_func_three(C,Q)
    elif func == 4:
        synthetic_img = gen_func_four(C,S)
    elif func == 5:
        synthetic_img = gen_func_five(C,S)

    return synthetic_img

if __name__ == '__main__':
    #input image name
    filename = input().rstrip()
    R = np.load(filename)
    #image size C
    C = int(input())
    #function choose (1,2,3,4,5)
    func = int(input())
    # generation parameter
    Q = int(input())
    # lateral size N
    N = int(input())
    #number of bits per pixel B
    B = int(input())
    #random function seed S
    S = int(input())

    #generate image
    img = generate_image(func, C, Q, S)
    #downscalling image
    down_img = downsampling(img, C,N)
    #apply quantization with B bits
    quant_img = quantization(down_img, B)
    #calculate difference between two images
    diff = difference_img(R, quant_img)
    print(format(diff, '.4f'))