import numpy as np
import matplotlib.pyplot as plt
import scipy.spatial.distance
import bats

# first, generate a circle
n = 100
X = np.random.normal(size=(n,2))
X = X / np.linalg.norm(X, axis=1).reshape(-1,1)
X = X + np.random.normal(size=(n,2), scale = 0.1 )
fig = plt.scatter(X[:,0], X[:,1])
fig.axes.set_aspect('equal')
plt.savefig('figures/RipsEx_data.png')

# use bats to compute distances internally
data = bats.DataSet(bats.Matrix(X)) # put into a bats.DataSet
dist = bats.Euclidean()  # distance we would like to use
F = bats.RipsFiltration(data, dist, np.inf, 2) # generate a RipsFiltration

R = bats.reduce(F, bats.F2()) # reduce with F2 coefficients
ps = []
for d in range(R.maxdim()):
    ps.extend(R.persistence_pairs(d))

fig, ax = bats.persistence_diagram(ps, tmax = 2.0)
plt.savefig('figures/RipsEx_pd_euc.png')

# use pairwise distance matrix

# method 1: use bats to get pairwise distances
data = bats.DataSet(bats.Matrix(X)) # put into a bats.DataSet
dist = bats.Euclidean()  # distance we would like to use
pdist = dist(data, data) # returns a bats.Matrix of pairwise distances

# method 2: use scipy to get pairwise distances
pdist_sp = scipy.spatial.distance.squareform(scipy.spatial.distance.pdist(X, 'euclidean'))
pdist = bats.Matrix(pdist_sp)

F = bats.RipsFiltration(pdist, np.inf, 2) # generate a filtraiton on pariwise distances
R = bats.reduce(F, bats.F2()) # reduce with F2 coefficients
ps = []
for d in range(R.maxdim()):
    ps.extend(R.persistence_pairs(d))

fig, ax = bats.persistence_diagram(ps, tmax = 2.0)
plt.savefig('figures/RipsEx_pd.png')

# greedy subsampling

# inds is sequence of indices selected by greedy landmarking
# dists[k] is hausdorff distance from X[inds[:k]] to X
inds, dists = bats.greedy_landmarks_hausdorff(pdist, 0)

k = 40 # we'll landmark 40 points
# print('hausdorff distance is {}'.format(dists[k]))
fig = plt.figure()
ret = plt.scatter(X[inds[:k],0], X[inds[:k],1])
fig.suptitle('hausdorff distance: {:.3f}'.format(dists[k]))
ret.axes.set_aspect('equal')
plt.savefig('figures/RipsEx_data_landmark.png')

# use scipy to get pairwise distances
pdist_sp = scipy.spatial.distance.squareform(scipy.spatial.distance.pdist(X[:k], 'euclidean'))
pdist = bats.Matrix(pdist_sp)

F = bats.RipsFiltration(pdist, np.inf, 2) # generate a filtraiton on pariwise distances
R = bats.reduce(F, bats.F2()) # reduce with F2 coefficients
ps = []
for d in range(R.maxdim()):
    ps.extend(R.persistence_pairs(d))

fig, ax = bats.persistence_diagram(ps, tmax = 2.0)
plt.savefig('figures/RipsEx_pd_landmark.png')

import plotly
import plotly.graph_objects as go

# use bats to get pairwise distances
data = bats.DataSet(bats.Matrix(X)) # put into a bats.DataSet
dist = bats.L1Dist()  # distance we would like to use
pdist = dist(data, data) # returns a bats.Matrix of pairwise distances

pdist_np = np.array(pdist) # numpy array of pairwise distances

F = bats.RipsFiltration(pdist, np.inf, 2) # generate a filtraiton on pariwise distances
R = bats.reduce(F, bats.F2()) # reduce with F2 coefficients

# get longest H1 pair
ps1 = R.persistence_pairs(1)
lens = [p.death() - p.birth() for p in ps1] # find longest length pair
ind = np.argmax(lens)
pair = ps1[ind]

def plot_representative_2D(X, F, R, pair, D, thresh=None, **kwargs):
    """
    Plot H1 represnetative on 2D scatter plot

    plot representative
    X: 2-dimensional locations of points
    F: bats FilteredSimplicialComplex
    R: bats ReducedFilteredChainComplex
    pair: bats PersistencePair
    D: N x N distance matrix
    thresh: threshold parameter
    kwargs: passed onto figure layout
    """
    if thresh is None:
        thresh = pair.birth()

    fig = go.Figure()
    fig.add_trace(go.Scatter(
        x=X[:,0], y=X[:,1],
        mode='markers',
    ))
    edge_x = []
    edge_y = []
    N = X.shape[0]
    for i in range(N):
        for j in range(N):
            if D[i, j] <= thresh:
                edge_x.extend([X[i,0], X[j,0], None])
                edge_y.extend([X[i,1], X[j,1], None])

    fig.add_trace(go.Scatter(
        x=edge_x, y=edge_y,
        line=dict(width=0.5, color='#888'),
        hoverinfo='none',
        mode='lines')
     )

    edge_x = []
    edge_y = []
    r = R.representative(pair)
    nzind = r.nzinds()
    cpx = F.complex()
    for k in nzind:
        [i, j] = cpx.get_simplex(1, k)
        if D[i, j] <= thresh:
            edge_x.extend([X[i,0], X[j,0], None])
            edge_y.extend([X[i,1], X[j,1], None])
    fig.add_trace(go.Scatter(
        x=edge_x, y=edge_y,
        line=dict(width=2, color='red'),
        hoverinfo='none',
        mode='lines')
     )
    fig.update_layout(**kwargs)
    return fig

fig = plot_representative_2D(X, F, R, pair, pdist_np, width=800, height=800)
fig.write_image('figures/H1_rep.png')


from scipy.stats import pearsonr
from math import sqrt
from math import isnan

# Получить список фильмов, оцененных обоими
def similar_films(critics_dict, person1, person2):
    # создаем пустой список фильмов
    sim_film = []

    # для всех фильмов у person1...
    for film in critics_dict[person1]:
        # если такой фильм оценивался person 2...
        if film in critics_dict[person2]:
            # добавляем этот фильм в список sim_film
            sim_film.append(film)

    return sim_film

#Подсчета сходства двух критиков на основе расстояния Евклида
def sim_distance(critics_dict, person1, person2):
    # используем написанную функцию для получения фильмов, оцененных обоими критиками
    sim_films = similar_films(critics_dict, person1, person2)

    # если нет ни одной общей оценки, то есть длина sim_film равна 0 - вернуть 0
    if len(sim_films) == 0:
        return 0

    # для каждого фильма из sim_film посчитаем евклидово расстояние и просуммируем все полученные значения
    sum_of_euclead_dist = 0

    for film in sim_films:
        sum_of_euclead_dist = sum_of_euclead_dist + pow(critics_dict[person1][film] - critics_dict[person2][film], 2)

    # считаем похожесть
    score = 1 / (1 + sum_of_euclead_dist)

    return score

# Возвращает коэффициент корреляции Пирсона между person1 и person2
def sim_pearson(critics_dict, person1, person2):
    # получить список фильмов, оцененных обоими критиками
    sim_films = similar_films(critics_dict, person1, person2)

    # если нет ни одной общей оценки, вернуть 0
    if len(sim_films) < 2:
        return 0

    # получим оценки критиков для фильмов из пересечения
    scores1 = []
    scores2 = []
    for film in sim_films:
        scores1.append(critics_dict[person1][film])
        scores2.append(critics_dict[person2][film])

    # посчитаем коэффициент корреляции
    # берем [0] от результата, так как функция pearsonr() возвращает еще значение p-value
    r = pearsonr(scores1, scores2)[0]

    # если вдруг было деление на ноль и функция вернула nan, то присваиваем ноль
    if isnan(r):
        r = 0
    return r

# возвращает топ-n наиболее похожих на person человек из critics_dict
def topMatches(critics_dict, person, n=5, similarity=sim_pearson):
    # инициализируем лист оценок похожести
    scores = []

    # для каждого человека в словаре критиков
    for other in critics_dict:

        # если этот человек не тот, для которого мы подбираем соответствия
        if other != person:
            # добавить в scores похожесть, вычисленную с помощью функции, записанной в similarity, и имя человека
            scores.append((similarity(critics_dict, person, other), other))

    # отсортировать список по убыванию оценок
    scores.sort(reverse=True)  # аргумент reverse=True отвечает за убывающий порядок

    # возвращаем первые n человек
    return scores[0:n]

# Получить рекомендации для заданного человека, пользуясь взвешенным средним
# оценок, данных всеми остальными пользователями
def getRecommendations(prefs, person, similarity=sim_pearson):

    # инициализируем словари
    totals = {}
    simSums = {}

    # для каждого пользователя
    for other in prefs:

    # сравнивать person с person не нужно
        if other == person:
            continue
        # считаем похожесть
        sim = similarity(prefs, person, other)

        # игнорировать нулевые и отрицательные значения похожести
        if sim <= 0:
            continue

        # для каждого фильма в оцененных other
        for item in prefs[other]:

            # оценивать только фильмы, которые я еще не смотрел
            if item not in prefs[person] or prefs[person][item] == 0:

                # коэффициент подобия * Оценка
                totals.setdefault(item,0)
                totals[item] = totals[item] + prefs[other][item]*sim

                # cумма коэффициентов подобия
                simSums.setdefault(item,0)
                simSums[item] = simSums[item] + sim
    # создать нормированный список
    rankings = [(total/simSums[item], item) for item, total in totals.items( )]

    # вернуть отсортированный список
    rankings.sort(reverse=True)
    return rankings

# обращает матрицу предпочтений, чтобы строки соответствовали образцам
def transformPrefs(critics_dict):

    # инициилизируем новый словарь с фильмами и их оценками
    result = {}

    # для каждого критика в словаре
    for person in critics_dict:

        # для каждого оцененного им фильма
        for item in critics_dict[person]:

            # добавляем такой объект в словарь
            result.setdefault(item, {})
            # меняеи местами человека и предмет
            result[item][person] = critics_dict[person][item]
    return result

# Создать словарь, содержащий для каждого образца те образцы, которые больше всего похожи на него.
def calculateSimilarItems(prefs, n=10):

    result = {}
    # обращаем матрицу предпочтений, чтобы строки соответствовали образцам
    itemPrefs = transformPrefs(prefs)

    c = 0
    for item in itemPrefs:
        # проверка состояния для больших наборов данных
        c = c + 1
        if c % 100 == 0: print("%d / %d" % (c,len(itemPrefs)))

        # найти образцы, максимально похожие на данный
        scores = topMatches(itemPrefs, item, n=n, similarity=sim_distance)
        result[item] = scores
    return result

# Получить рекомендации для заданного человека, пользуясь взвешенным средним
# оценок, похожих на оцененные пользователем объектов

def getRecommendedItems(prefs, itemMatch, user):

    # инициализируем словари
    userRatings = prefs[user]
    scores = {}
    totalSim = {}

    # цикл по образцам, оцененным данным пользователем (хранятся в userRatings)
    for (item, rating) in userRatings.items():

        # цикл по образцам, похожим на данный (хранятся в словаре itemMatch)
        for (similarity, item2) in itemMatch[item]:
            # пропускаем объект, если пользователь уже оценивал данный образец
            if item2 in userRatings:
                continue
            # взвешенная суммы оценок, умноженных на коэффициент подобия
            scores.setdefault(item2, 0)
            scores[item2] = scores[item2] + similarity * rating
            # сумма всех коэффициентов подобия
            totalSim.setdefault(item2, 0)
            totalSim[item2] = totalSim[item2] + similarity
            if totalSim[item2] == 0:
                totalSim[item2] = 0.0000001  # чтобы избежать деления на ноль
    # делим каждую итоговую оценку на взвешенную сумму, чтобы вычислить среднее
    rankings = [(score / totalSim[item], item) for item, score in scores.items()]

    # возвращает список rankings, отсортированный по убыванию
    rankings.sort(reverse=True)
    return rankings

# Функция для загрузки данных MovieLens
def loadMovieLens():
    # получить названия фильмов
    movies = {}
    for line in open('u.item', encoding = "ISO-8859-1"):
        (id, title) = line.split('|')[0:2]
        movies[id] = title
    # загрузить данные
    prefs = {}
    for line in open('u.data'):
        (user, movieid, rating, ts) = line.split('\t')
        prefs.setdefault(user, {})
        prefs[user][movies[movieid]] = float(rating)
    return prefs


""" Helper functions for constructing design regressors
"""
from __future__ import division

import numpy as np

def dct_ii_basis(volume_times, order=None, normcols=False):
    """ DCT II basis up to order `order`

    See: https://en.wikipedia.org/wiki/Discrete_cosine_transform#DCT-II

    By default, basis not normalized to length 1, and therefore, basis is not
    orthogonal.  Normalize basis with `normcols` keyword argument.

    Parameters
    ----------
    volume_times : array-like
        Times of acquisition of each volume.  Must be regular and continuous
        otherwise we raise an error.
    order : None or int, optional
        Order of DCT-II basis.  If None, return full basis set.
    normcols : bool, optional
        If True, normalize columns to length 1, so return orthogonal
        `dct_basis`.

    Returns
    -------
    dct_basis : array
        Shape ``(len(volume_times), order)`` array with DCT-II basis up to
        order `order`.

    Raises
    ------
    ValueError
        If difference between successive `volume_times` values is not constant
        over the 1D array.
    """
    N = len(volume_times)
    if order is None:
        order = N
    if not np.allclose(np.diff(np.diff(volume_times)), 0):
        raise ValueError("DCT basis assumes continuous regular sampling")
    n = np.arange(N)
    cycle = np.pi * (n + 0.5) / N
    dct_basis = np.zeros((N, order))
    for k in range(0, order):
        dct_basis[:, k] = np.cos(cycle * k)
    if normcols:  # Set column lengths to 1
        lengths = np.ones(order) * np.sqrt(N / 2.)
        lengths[0:1] = np.sqrt(N)  # Allow order=0
        dct_basis /= lengths
    return dct_basis

def dct_ii_cut_basis(volume_times, cut_period):
    """DCT-II regressors with periods >= `cut_period`

    See: http://en.wikipedia.org/wiki/Discrete_cosine_transform#DCT-II

    Parameters
    ----------
    volume_times : array-like
        Times of acquisition of each volume.  Must be regular and continuous
        otherwise we raise an error.
    cut_period: float
        Cut period (wavelength) of the low-pass filter (in time units).

    Returns
    -------
    cdrift: array shape (n_scans, n_drifts)
        DCT-II drifts plus a constant regressor in the final column.  Constant
        regressor always present, regardless of `cut_period`.
    """
    N = len(volume_times)
    hfcut = 1./ cut_period
    dt = volume_times[1] - volume_times[0]
    # Such that hfcut = 1/(2*dt) yields N
    order = int(np.floor(2 * N * hfcut * dt))
    # Always return constant column
    if order == 0:
        return np.ones((N, 1))
    basis = np.ones((N, order))
    basis[:, :-1] = dct_ii_basis(volume_times, order, normcols=True)[:, 1:]
    return basis


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

from scipy.integrate import quad
import numpy as np
import bigfloat
import argparse

class Stikers():
    """Calculate the amount paid for n stikers while exchanging with f friends
    n/f[*+]integrate_0^inf(1-(1-(1+x/2!+x**2)/e^x))dx
    Based on the video:
        The Math (and money) of Soccer Stickers - Numberphile
        (https://youtu.be/aKPkQCys86c)
    and:
       Soccer Stickers (extra math bit) - Numberphile
        (https://youtu.be/iMSitUaoS3I)"""

    def __init__(self, n, f, spp, valueperunit, valuetohigher, highersign, lastbuy):
        self.n = n
        self.f = f+1 #to be semanticly correct, one represents you, 2 is you and a friend
        self.spp = spp  # stickers per pack
        self.valueperunit = valueperunit
        self.valuetohigher = valuetohigher
        self.highersign = highersign
        self.lastbuy = lastbuy

    def lasum(self, x):
        return 1 + sum(bigfloat.pow(x, f) / bigfloat.factorial(f) for f in range(1, self.f))

    def calc_ExpectedToBuy(self, stop=0):
        return sum((self.n * 1.) / dec for dec in range(stop + 1, self.n + 1)[::-1])

    def integrand(self, x):
        return 1.0 - (1.0 - self.lasum(x)
                      / bigfloat.exp(x, bigfloat.precision(100)))**self.n

    def calc(self):
        try:
            a = quad(self.integrand, 0, np.inf)
            b = self.n / self.f * 1.
            title = "You and {} friend{}".format(self.f-1, "s" if self.f > 2 else "")
            print "\n=={}==".format("Alone" if self.f == 1 else title)
            print "Stickers you buy:\t{:.2f}\n" \
                  "         you pay:\t{}{:.2f}".format((b * a[0]), self.highersign,
                                                       ((b * a[0]) *
                                                        self.valueperunit)
                                                       / self.valuetohigher)
        except ZeroDivisionError:
            print("Cannot devide by zero.")

    def extrainfo(self):
        try:
            expected = self.calc_ExpectedToBuy()
            expectedWithBuy = self.calc_ExpectedToBuy(self.lastbuy)
            print "\n==Information=="
            print "To complete the album alone is expected " \
                  "you to buy {:.2f} stickers ({:.2f} packs)" \
                  ", paying {}{:.2f} for it.".format(expected, expected / self.spp,
                                                   self.highersign,
                                                   (expected *
                                                    self.valueperunit)
                                                   / self.valuetohigher)
            print "You will need to pay " \
                  "{}{:.2f} for {:.2f} stickers if you plan to buy the last " \
                  "{}{} directly " \
                  "from the company.".format(self.highersign,
                                             (expectedWithBuy *
                                              self.valueperunit)
                                             / self.valuetohigher,
                                             expectedWithBuy,
                                             self.lastbuy if self.lastbuy > 1 else "\b",
                                             " stickers" if self.lastbuy > 1 else " sticker")
            print "N=n*(log(n)+Euler constant) = " \
                  "{}".format(self.n * bigfloat.log(self.n) +
                              bigfloat.const_euler())
            print "---------------"
        except ZeroDivisionError:
            print("Cannot devide by zero.")

if __name__ == '__main__':
    print("=Stickers=")
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-n', type=int, help='Number of stickers', required=True)
    parser.add_argument(
        '-f', type=int, help='Number of friends', required=True)
    parser.add_argument('-p', '--spp', type=int,
                        dest='spp', default=5, help='Number of stickers per pack')
    parser.add_argument('-u', '--unit', type=int,
                        dest='valueperunit', default=16, help='Value of a single unit, like penny')
    parser.add_argument('-b', '--basecurrency', default=100, type=int,
                        dest='valuetohigher', help='Ex: from pence to pounds')
    parser.add_argument('-s', '--sign', default='£', dest='highersign',
                        help='Currency sign')
    parser.add_argument('-l', '--lastbuy', type=int, default=50, dest='lastbuy',
                        help='How many stickers you can buy directly from the company')

    args = parser.parse_args()
    s = Stikers(**vars(args))
    s.extrainfo()
    s.calc()


import scipy.linalg as la
import numpy as np

class LQR(object):
    def __init__(self) -> None:
        pass

    def solve_DARE(self, A, B, Q, R):
        """
        solve a discrete time_Algebraic Riccati equation (DARE)
        """
        X = Q
        maxiter = 150
        eps = 0.01

        for i in range(maxiter):
            Xn = A.T @ X @ A - A.T @ X @ B @ \
                la.inv(R + B.T @ X @ B) @ B.T @ X @ A + Q
            if (abs(Xn - X)).max() < eps:
                break
            X = Xn

        return Xn

    def dlqr(self, A, B, Q, R):
        """Solve the discrete time lqr controller.
        x[k+1] = A x[k] + B u[k]
        cost = sum x[k].T*Q*x[k] + u[k].T*R*u[k]
        # ref Bertsekas, p.151
        """

        # first, try to solve the ricatti equation
        X = self.solve_DARE(A, B, Q, R)

        # compute the LQR gain
        K = la.inv(B.T @ X @ B + R) @ (B.T @ X @ A)

        eigVals, eigVecs = la.eig(A - B @ K)

        return K, X, eigVals


from sympy import symbols, sympify, factor, pprint, init_printing, plot
from sympy.core import SympifyError

init_printing( order = 'rev-lex' )

def graph_system( expr1, expr2 ):

	y = symbols( 'y' )

	expr1_sln = solve( expr1, y )
	expr2_sln = solve( expr2, y )

	p = plot( expr1_sln[ 0 ], expr2_sln[ 0 ], legend = True, show = False )

	p[ 0 ].line_color = 'b'
	p[ 1 ].line_color = 'r'

	sln = solve( ( expr1, expr2 ), symbols( 'x, y' ) )

	pprint( sln )

	p.show()

if __name__== '__main__':

	try:
		expr1 = input( 'Enter an expression in terms of x and y: ' )
		expr2 = input( 'Enter an expression in terms of x and y: ' )

		expr1 = sympify( expr1 )
		expr2 = sympify( expr2 )
	except SympifyError:
		print( 'Invalid Input' )
	else:
		graph_system( expr1, expr2 )


import numpy as np

def generate_gaussian_heatmap(shape, xy, sigma):
    """

    Args:
        shape: (height, width)

    """
    x, y = xy
    grid_x = np.tile(np.arange(shape[1]), (shape[0], 1))
    grid_y = np.tile(np.arange(shape[0]), (shape[1], 1)).transpose()
    grid_distance = (grid_x - x) ** 2 + (grid_y - y) ** 2
    gaussian_heatmap = np.exp(-0.5 * grid_distance / sigma**2)
    return gaussian_heatmap

def generate_heatmaps(img, poses, sigma=7.0):
    """

    Args:
        img (`~numpy.ndarray`):
             shape of img is ``(height, width, channel)``
        poses (`~numpy.ndarray`):
             shape of poses is ``(number of instances, num_joint, 3)``
        sigma (`~float`):
              sigma of gaussian

    """
    _, num_joint, _ = poses.shape
    height, width, _ = img.shape
    heatmaps = np.zeros((0, height, width))
    sum_heatmap = np.zeros((height, width))
    for joint_index in range(num_joint):
        heatmap = np.zeros((height, width))
        for pose in poses:
            if pose[joint_index, 2] > 0:
                jointmap = generate_gaussian_heatmap(
                    (height, width), pose[joint_index][:2], sigma)
                heatmap[jointmap > heatmap] = jointmap[jointmap > heatmap]
                sum_heatmap[jointmap >
                            sum_heatmap] = jointmap[jointmap > sum_heatmap]
        heatmaps = np.vstack((heatmaps, heatmap.reshape((1, height, width))))
    bg_heatmap = 1 - sum_heatmap  # background channel
    heatmaps = np.vstack((heatmaps, bg_heatmap[None]))
    return heatmaps.astype('f')


#################################################################
# Name:     SolveLinear.py                                      #
# Authors:  Michael Battaglia                                   #                                        #
# Function: Program contains routines for solving linear        #
#           systems of equations.                               #
#################################################################

#essential imports
import numpy as np
from time import process_time
import matplotlib.pyplot as plt

#function: Implement Gaussian Elimination
def GaussElim(A_in,v_in):
    #np.copy A and v to temporary variables using np.copy command
    A = np.copy(A_in)
    v = np.copy(v_in)
    N = len(v)

    #for each mth element in diagonal
    for m in range(N):
        # Divide by the diagonal element
        div = A[m,m]
        A[m,:] /= div
        v[m] /= div

        # Now subtract from the lower rows
        for i in range(m+1,N):
            mult = A[i,m]
            A[i,:] -= mult*A[m,:]
            v[i] -= mult*v[m]

    # Backsubstitution
    #create an array of the same type as the input array
    x = np.empty(N,dtype=v.dtype)
    for m in range(N-1,-1,-1):
        x[m] = v[m]
        for i in range(m+1,N):
            x[m] -= A[m,i]*x[i]
    return x

#function: Implement Partial Pivot
def PartialPivot(A_in,v_in):
    #np.copy A and v to temporary variables using np.copy command
    A = np.copy(A_in)
    v = np.copy(v_in)
    N = len(v)

    #for each mth element in diagonal
    for m in range(N):
        # Partial pivot on mth column from A[m,m]
        col = A[:,m]
        row = m + np.argmax(np.square(col[m:]))
        # Swap row m with optimal row
        A[m,:], A[row,:] = np.copy(A[row,:]), np.copy(A[m,:])
        v[m], v[row] = v[row], v[m]

        # Divide by the diagonal element
        div = A[m,m]
        A[m,:] /= div
        v[m] /= div

        # Now subtract from the lower rows
        for i in range(m+1,N):
            mult = A[i,m]
            A[i,:] -= mult*A[m,:]
            v[i] -= mult*v[m]

    # Backsubstitution
    #create an array of the same type as the input array
    x = np.empty(N,dtype=v.dtype)
    for m in range(N-1,-1,-1):
        x[m] = v[m]
        for i in range(m+1,N):
            x[m] -= A[m,i]*x[i]
    return x

#function: Implement LU Decomposition
def LUdecomp(A_in):
    #Length of vector inputs
    N = A.shape[1]
    #A will eventually become U
    U = np.copy(A_in)
    #initiate L array
    L = np.zeros(U.shape)
    #initiate list to record swaps
    swaps = []

    #for each mth element in diagonal
    for m in range(N):
        #record state in L array
        L[:,m][m:] = U[:,m][m:]

        # Partial pivot on mth column from A[m,m]
        col = U[:,m]
        row = m + np.argmax(np.square(col[m:]))
        swaps.append(row)
        # Swap row m with optimal row
        U[m,:], U[row,:] = np.copy(U[row,:]), np.copy(U[m,:])
        L[m,:], L[row,:] = np.copy(L[row,:]), np.copy(L[m,:])

        # Divide by the diagonal element
        div = U[m,m]
        U[m,:] /= div

        # Now subtract from the lower rows
        for i in range(m+1,N):
            mult = U[i,m]
            U[i,:] -= mult*U[m,:]
    #Conlusion of LU decomposition
    return L, U, swaps

#function: Apply permutation operation on vector
def permute(op, v_in):
    #input vector to be permuted
    v = np.copy(v_in)
    N = len(v)
    #permute elements of v according to operator
    for m in range(N):
        v[m], v[op[m]] = v[op[m]], v[m]
    return v

#function: Implement LU Backsubstitution
def LUbacksub(L_in, U_in, v_in):
    #input vector (permuted accordingly)
    v = np.copy(v_in)
    N = len(v)
    #LU decomposition matrices
    L = np.copy(L_in)
    U = np.copy(U_in)

    # Backsubstitution for Ly = v
    #create an array of the same type as the input array
    y = np.empty(N,dtype=v.dtype)
    for m in range(N):
        y[m] = v[m]/L[m,m]
        for i in range(m):
            y[m] -= L[m,i]*y[i]/L[m,m]

    # Backsubstitution for Ux = y
    #create an array of the same type as the input array
    x = np.empty(N,dtype=y.dtype)
    for m in range(N-1,-1,-1):
        x[m] = y[m]/U[m,m]
        for i in range(m+1,N):
            x[m] -= U[m,i]*x[i]/U[m,m]
    return x

#function: main
if __name__ == '__main__':
    #simple diagnostic of linear equation solver
    A = np.array([[ 2, 1, 4, 1],
                  [ 3, 4,-1,-1],
                  [ 1,-4, 1, 5],
                  [ 2,-2, 1, 3]], float)
    v = np.array([-4, 3, 9, 7], float)
    print("matrix A")
    print(A)
    print("vector v")
    print(v)
    print("Solve Ax = v")
    #solve using gaussian elimination
    x_gaus = GaussElim(A,v)
    print("By gaussian elimination")
    print("x =",x_gaus)
    #solve using partial pivot
    x_ppiv = PartialPivot(A,v)
    print("By gaussian elimination with partial pivot")
    print("x =",x_ppiv)
    #solve using LU decomposition
    L, U, op = LUdecomp(A)
    x_LUdc = LUbacksub(L,U,permute(op,v))
    print("By LU decomposition with partial pivot")
    print("x =",x_LUdc)
    #check diagnostic success
    x_sol = np.array([2, -1, -2, 1], float)
    print("In reality")
    print("x =",x_sol)

    #advanced diagnostic of linear equation solver
    #trial N, sizes of matrices
    N_trials = np.arange(10,1000,10)
    #error as a function of N
    e_gaus = np.zeros(len(N_trials))
    e_ppiv = np.zeros(len(N_trials))
    e_LUdc = np.zeros(len(N_trials))
    #timing as a function of N
    t_gaus = np.zeros(len(N_trials))
    t_ppiv = np.zeros(len(N_trials))
    t_LUdc = np.zeros(len(N_trials))

    print("Solving "+str(len(N_trials))+" linear systems")
    #for each trial size N
    for i, N in enumerate(N_trials):
        #generate a random matrix
        A = np.random.rand(N,N)
        #perform QR factorization
        Q, R = np.linalg.qr(A)
        #random orthogonal (nonsingular) matrix
        A = Q
        #generate a random vector
        v = np.random.rand(N)

        print("Solving "+str(i+1)+"/"+str(len(N_trials)))
        #solve using gaussian elimination
        start = process_time()
        x_gaus = GaussElim(A,v)
        end = process_time()
        e_gaus[i] = np.mean(abs(v - np.dot(A,x_gaus)))
        t_gaus[i] = end - start
        #solve using partial pivot
        start = process_time()
        x_ppiv = PartialPivot(A,v)
        end = process_time()
        e_ppiv[i] = np.mean(abs(v - np.dot(A,x_ppiv)))
        t_ppiv[i] = end - start
        #solve using LU decomposition
        start = process_time()
        L, U, op = LUdecomp(A)
        x_LUdc = LUbacksub(L,U,permute(op,v))
        end = process_time()
        e_LUdc[i] = np.mean(abs(v - np.dot(A,x_LUdc)))
        t_LUdc[i] = end - start

    #plot dependence of error on N
    plt.plot(N_trials,e_gaus,label="gaus")
    plt.plot(N_trials,e_ppiv,label="ppiv")
    plt.plot(N_trials,e_LUdc,label="LUdc")
    plt.title("Error vs Matrix size")
    plt.ylabel("error")
    plt.xlabel("N")
    plt.yscale('log')
    plt.xscale('log')
    plt.legend()
    plt.show()

    #plot dependence of timing on N
    plt.plot(N_trials,t_gaus,label="gaus")
    plt.plot(N_trials,t_ppiv,label="ppiv")
    plt.plot(N_trials,t_LUdc,label="LUdc")
    plt.title("Execution time vs Matrix size")
    plt.ylabel("exec time [s]")
    plt.xlabel("N")
    plt.yscale('log')
    plt.xscale('log')
    plt.legend()
    plt.show()


import numpy as np
from matplotlib import pyplot as plt

class PCA:
    def fit(self, X):

        '''
        fits sorted eigenvalues and eigenvectors to class attributes. same goes for variance and explained variance.
        '''

        n_samples = X.shape[0]
        # We center the data and compute the sample covariance matrix.
        X -= np.mean(X, axis=0)
        self.cov_matrix_ = np.dot(X.T, X) / (n_samples-1)
        #test = np.cov(X)

        #Negative values are ignored with eigh
        (self.eigvalues_, self.components_) = np.linalg.eigh(self.cov_matrix_)

        idx = self.eigvalues_.argsort()[::-1]
        self.eigvalues_ = self.eigvalues_[idx]
        self.components_ = self.components_[idx]
        self.variance_ = np.sum(self.eigvalues_)
        self.explained_variance_ = self.eigvalues_ / self.variance_

    def transform(self, X):
        #project data onto eigenvectors
        print(self.components_.shape, X.shape)
        self.projected_ = X @ self.components_.T
        return self.projected_

pca = PCA()

# Generate some dummy data
subsample = np.random.randn(69,2)*0.1
subsample[:,0] = subsample[:,0]*8
subsample[:,1] = subsample[:,0] + subsample[:,1]

pca.fit(subsample)

plt.scatter(subsample[:,0], subsample[:,1], edgecolor='none', alpha=0.5)
plt.quiver(pca.components_[0,0]*2, pca.components_[0,1]*2,
       angles='xy', scale_units='xy', scale=1, width=0.006)
plt.quiver(pca.components_[1,0]*2, pca.components_[1,1]*2,
       angles='xy', scale_units='xy', scale=1, width=0.006)
plt.gca().set_aspect('equal')
plt.show()


import random
from typing import Iterable, List

import numpy as np
import scipy.signal
import torch

def discount_cumulative_sum(vector: np.ndarray, discount: float) -> np.ndarray:
    """
    Compute discounted cumulative sums of vector.

    :param vector: (np.ndarray) An target vector
    e.g. [x0,
          x1,
          x2]
    :param discount: (float) The discount factor for the cumulative return
    :return: (np.ndarray) discounted cumulative sums of an vector
    e.g. [x0 + discount * x1 + discount^2 * x2,
          x1 + discount * x2,
          x2]
    """
    return scipy.signal.lfilter([1], [1, -discount], vector[::-1], axis=0)[::-1]

def seed_random_generators(seed: int) -> None:
    """
    Set the seed of the pseudo-random generators (python, numpy, pytorch)

    :param seed: (int)
    """
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)

def gae(
    rewards: np.ndarray, gamma: float, values: np.ndarray, gae_lambda: float
) -> np.ndarray:
    """
    Compute Generalized Advantage Estimation (GAE)

    :param rewards: (np.ndarray) Rewards for all states
    :param gamma: (float) The discount factor for the cumulative return
    :param values: (np.ndarray) Values for all states
    :param gae_lambda: (float) A smoothing parameter for reducing the variance
    :return gaes: (np.ndarray) GAEs for all states
    """
    deltas: np.ndarray = rewards[:-1] + gamma * values[1:] - values[:-1]
    gaes: np.ndarray = discount_cumulative_sum(deltas, gamma * gae_lambda)

    return gaes

def unflatten_tensors(
    flattened: np.ndarray, tensor_shapes: List[torch.Size]
) -> List[torch.Tensor]:
    """
    Unflatten a flattened tensors into a list of tensors.

    :param flattened: (np.ndarray) Flattened tensors.
    :param tensor_shapes: (List[torch.Size]) Tensor shapes.
    :return: (List[np.ndarray]) Unflattened list of tensors.
    """
    tensor_sizes = list(map(np.prod, tensor_shapes))
    indices = np.cumsum(tensor_sizes)[:-1]

    return [
        np.reshape(pair[0], pair[1])
        for pair in zip(np.split(flattened, indices), tensor_shapes)
    ]

def polyak_average(
    params: Iterable[torch.nn.Parameter],
    target_params: Iterable[torch.nn.Parameter],
    tau: float,
) -> None:
    """
    Perform Polyak averaging on target_params using params.

    :param params: (Iterable[torch.nn.Parameter]) The parameters to use to update the target params
    :param target_params: (Iterable[torch.nn.Parameter]) The parameters to update
    :param tau: (float) the soft update coefficient ("Polyak update", between 0 and 1)
    """
    with torch.no_grad():
        for param, target_param in zip(params, target_params):
            target_param.data.copy_((1.0 - tau) * target_param.data + tau * param.data)


import numpy as np

def euclidianDist(data, centers):
    '''Compute the euclidian distance of each data point to each center'''
    return np.sqrt(np.sum((data - centers[:, np.newaxis]) ** 2, axis=2)).T

def manhattanDist(data, centers):
    '''Compute the Manhattan distance of each data point to each center'''
    return np.sum(np.abs(data - centers[:, np.newaxis]), axis=2).T


""" Orbital Mechanics for Engineering Students Example 1.18
Question:
Solve
    xDDot + 2*zeta*omegaN*xDot + omegaN**2*x = F0/m*sin(omega*t)
numerically using the RK method.
Parameter values:
    - m = 1 kg
    - omegaN = 1 rad/s
    - zeta = 0.03
    - F0 = 1 N
    - omega = 0.4 rad/s
Initial conditions:
    x = xDot = 0
Written by: J.X.J. Bannwarth
"""
import numpy as np
import matplotlib.pyplot as plt
from orbitutils.solvers import rk14
import collections

# Exact response - answer from Example 1.17
def CartSystemExactResponse(t, m=1.0, omegaN=1.0, zeta=0.03, F0=1.0,
                            omega=0.4):
    x = np.exp(-0.03 * t) * (
        0.03399 * np.cos(0.9995 * t) - 0.4750 * np.sin(0.9995 * t)) + (
            1.190 * np.sin(0.4 * t) - 0.03399 * np.cos(0.4 * t))
    return x

# Equation for the derivatives of the states
def Rates(t, y, m=1.0, omegaN=1.0, zeta=0.03, F0=1.0, omega=0.4):
    f = np.zeros((2, ))
    f[0] = y[1]
    f[1] = ((F0 / m) * np.sin(omega * t) -
            (omegaN**2) * y[0] - 2 * zeta * omegaN * y[1])
    return f

# Plotting functions
def PlotResponse(t, y, tExact=None, xExact=None):
    nStates = y.shape[1]
    _, axs = plt.subplots(nStates)
    units = ["m", "m/s"]
    for idx, ax in enumerate(axs):
        ax.plot(t, y[:, idx], label="RK")
        if (idx == 0) and xExact is not None:
            ax.plot(tExact, xExact, label="Exact")
            ax.legend()
        ax.grid()
        ax.set_ylabel(f"$y_{idx+1}$ ({units[idx]})")
    axs[-1].set_xlabel("Time (s)")
    plt.show()

def PlotFig123(tOut, xOut, sets, tExact, xExact):
    # Note that for some reason the textbook normalises both axes - we replicate
    # this here for consistency
    _, axs = plt.subplots(5, figsize=(8,12))

    # First plot: exact answer
    axs[0].plot(tExact / tExact[-1], xExact / np.max(xExact), color="black")
    axs[0].set_title("Exact")

    # Other plots: sort by method
    for m, ax in enumerate(axs[1:]):
        rk = m + 1
        colors = ["black", "red"]
        for n, set in enumerate(sets):
            if set.order == rk:
                ax.plot(
                    tOut[n] / tOut[n][-1],
                    xOut[n] / np.max(np.abs(xOut[n])),
                    label=f"h = {set.h}",
                    color=colors[0])
                colors.pop(0)
        ax.set_title(f"RK{rk}")
        ax.legend()
        ax.set_ylim(bottom=-1.,top=1.)
    plt.tight_layout()
    plt.show()

if __name__ == "__main__":
    # Title
    print("Orbital Mechanics for Engineering Students Example 1.18")

    # (1) Solve individual example
    # Parameters
    y0 = np.array([0, 0])
    tMax = 110.0
    h = 1.0
    order = 4

    # Solve equation numerically
    ys, ts = rk14(Rates, y0, tMax, h, order)

    # Get exact answer
    tExact = np.linspace(0.0, tMax, 5000)
    xExact = CartSystemExactResponse(tExact)

    # Plot results
    PlotResponse(ts, ys, tExact, xExact)

    # (2) Run through different order and h combinations to reproduce Figure 1.23
    TestSet = collections.namedtuple("TestSet", "order h")
    sets = [
        TestSet(1, 0.01), TestSet(1, 0.1), TestSet(2, 0.1), TestSet(2, 0.5),
        TestSet(3, 0.5), TestSet(3, 1.0), TestSet(4, 1.0), TestSet(4, 2.0)
    ]

    xOut = []
    tOut = []
    for set in sets:
        yOut, tOutTmp = rk14(Rates, y0, tMax, set.h, set.order)
        xOut.append(yOut[:, 0])
        tOut.append(tOutTmp)

    PlotFig123(tOut, xOut, sets, tExact, xExact)


# -*- coding: utf-8 -*-
"""
Created on Mon Oct 26 10:44:49 2020

@author: Larisa
"""

import sistemeEcuatiiLiniare as sel # si. ecuații liniare
import numpy as np

# Testăm metSubDesc
# Test 1
A = np.array([[1, 2, 3],
              [4, 5, 6]])
b = np.array([1, 4, 5])
tol = 10 ** (-16)

x = sel.metSubDesc(A, b, tol)
print(f'Test 1: {x}\n')

# Test 2
A = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])
b = np.array([[1], [4], [5]])
tol = 10 ** (-16)

x = sel.metSubDesc(A, b, tol)
print(f'Test 2: {x}\n')

# Test 3
A = np.array([[0, 2, 3],
              [0, 5, 6],
              [0, 0, 9]])
b = np.array([1, 4, 5])
tol = 10 ** (-16)

x = sel.metSubDesc(A, b, tol)
print(f'Test 3: {x}\n')

# Test 4
A = np.array([[1, 2, 3],
              [0, 5, 6],
              [0, 0, 8]])
b = np.array([1, 4, 5])
tol = 10 ** (-16)

x = sel.metSubDesc(A, b, tol)
print(f'Test 4: \n{x}')

# Verificare
print("Verificare 1:")
print(A@x)
print("Verificare 2:")
print(np.dot(A, x))
print('\n')

# Exercițiul 3.b)
A = np.array([[0, 1, 1],
              [2, 1, 5],
              [4, 2, 1]])
b = np.array([[3], [5], [1]])
x = sel.GaussFP(A, b, tol)
print(f'Soluția Sistemului 1 - GaussFP: \n{x}')
x = sel.GaussPP(A, b, tol)
print(f'Soluția Sistemului 1 - GaussPP: \n{x}')
print(f'Verificare (Ax - b = 0): \n{A@x-b}\n')

A = np.array([[0, 1, -2],
              [1, -1, 1],
              [1, 0, -1]])
b = np.array([[4], [6], [2]])
x = sel.GaussFP(A, b, tol)
print(f'Soluția Sistemului 2 - GaussFP: \n{x}')
x = sel.GaussPP(A, b, tol)
print(f'Soluția Sistemului 2 - GaussPP: \n{x}')
print(f'Determinantul Matricei A din Sistemul 2: {np.linalg.det(A)}\n') # determinantul matricei

# Exercițiul 3.c)
eps = 10 ** -20
tol1 = 10 ** -30
tol2 = 10 ** -16
A = np.array([[eps, 1],
              [1, 1]])
b = np.array([[1], [2]])
x = sel.GaussFP(A, b, tol1)
print(f'Soluție 1: \n{x}')
x = sel.GaussFP(A, b, tol2)
print(f'Soluție 2: \n{x}')

Calcul Numeric (CN)/Laborator/Laborator 3, 4/lab3.py


import numpy as np

def mean_square_error(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate MSE loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    MSE of given predictions
    """
    return np.power((y_true-y_pred), 2).mean()

def misclassification_error(y_true: np.ndarray, y_pred: np.ndarray, normalize: bool = True) -> float:
    """
    Calculate misclassification loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values
    normalize: bool, default = True
        Normalize by number of samples or not

    Returns
    -------
    Misclassification of given predictions
    """

    eq = np.sum(y_true != y_pred).__float__()
    if normalize:
        return eq/y_true.size
    else:
        return eq

def accuracy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate accuracy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Accuracy of given predictions
    """
    # total true labels of 1
    P = np.sum(y_true == 1)
    # total true labels of -1
    N = np.sum(y_true == -1)
    # True Positive: we predict a label of 1, and the true label is 1.
    TP = np.sum(np.logical_and(y_pred == 1, y_true == 1))
    # True Negative: we predict a label of -1, and the true label is -1.
    TN = np.sum(np.logical_and(y_pred == -1, y_true == -1))

    return (TP+TN)/(P+N)

def cross_entropy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate the cross entropy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Cross entropy of given predictions
    """
    raise NotImplementedError()


# -*- coding: utf-8 -*-
"""
Created on Mon Sep 23 10:02:57 2019

@author: hamil
"""

import numpy as np
import matplotlib.pyplot as plt
import numpy.random as rand
from numpy import histogram as hist

#define a function to calculate the integral using mean value
def meanvalue_Int(N, initial,final):
    #Generate the sampling values for x
    #for n in range(N):
    x = rand.uniform(initial,final, N)
        #calculate f(x) / y values for the function
    #y = np.square(x)
    y = x**2
        #calculate the sum and mean of the y_array
    sum_function = np.sum(y)
    mean_value = (final - initial) / N
        #calculate the result
    f_result = mean_value*sum_function

    return f_result, x, y

#test function
test, x_values, y_values = meanvalue_Int(10000, 0,10)
answer = test
print("This is the approx value:", answer)
plt.plot(x_values, y_values, '.')
plt.show()
#Given how we approached the function, the graph of the x and y values generated
#makes sense and it is what is expected. When running the function several times, the output
#value varies a quite a bit more than I thought it would.The output value ranges from
#315 to 341 whereas the analytical value is 333.333.. in the several times I ran the code.

#When increasing the sampling size to 10000, it actually brought the overall output to a more
#acceptable value. It actually decreases the error. The value ranges from 329 to 339 the several
#times I ran the code.

#Now run the simulation 100 times and calculate the mean and median values for the simulation
answer_array = []
for n in range(100):
    trial, x_array, y_array = meanvalue_Int(1000, 0,10)
    answer_array.append(trial)

mean_num = np.mean(answer_array)
median_num = np.median(answer_array)
st_div = np.std(answer_array)
print("Mean value of 100 runs:", mean_num)
print("Median value of 100 runs:", median_num)
print("Error in 100 trials:", st_div)
x_array = np.arange(0,100,1)
plt.hist(answer_array)
plt.plot()

#The mean and median values are relatively close to the analytical value where
# It is difficult to say which, mean or median, is better at estimating the actual value
#of the integral. There is still the spread in values with just using 1000 samples.

#When comparing my calculated error with equation 5.80 from the book,
st_div2 = 1/(np.sqrt(1000))*100
st_div3 = 1/ np.sqrt(1000)
print("book version for error:",st_div2)
print("error for large N:", st_div3)
#The two different values for the standard deviation are quite quite different
#than my calculated value for the standard deviation


# -*- coding: utf-8 -*-
"""
Created on Sun Jun 24 22:24:01 2018

@author: santhosh

-mcmm-

simple examples showing the use of monte carlo methods:
1. to generate samples from p(y), given p(y|x) and p(x)
2. to generate samples from a "posterior", p(x|y), given "likelihood", p(y|x)
   and "prior" p(x)
"""
import numpy as np
import matplotlib.pyplot as plt

def findx(c,r):
    """
    Returns the smallest index of the element(s) in c that is nearest to r

    Keyword arguements:
    c: cumulative sum of a probability function
    r: a value between 0.0 and 1.0
    """
    idxs = np.zeros(len(r), dtype=int)
    for i in range(len(r)):
        idxs[i] = int(np.argmin(c < r[i]))

    return idxs

def sample(labels, p, N):
    """
    samples random variables according to a given probability mass function

    Keyword arguments:
    labels: the values that the random variables can take (e.g. [0,1] or ['H','T'])
    p: probability mass function (p[i] >= 0 and p.sum() == 1.0)
    N: The number of samples

    Returns an array of random variables
    """
    c = p.cumsum()
    r = np.random.uniform(0,1,N)
    idxs = findx(c,r)
    x = labels[idxs]
    return x

if __name__ == '__main__':
    y_labels = np.array([0,1])
    x_labels = np.array([0,1])

    """
       p_x = p(x)
       p(x == 0) = 0.6 and p(x == 1) = 0.4
    """

    p_x = np.array([0.6,0.4])

    """
       p_y_x = p(y|x)
       p(y|x = 0) = 0.1, y == 0
                  = 0.9, y == 1

       p(y|x = 1) = 0.7, y == 0
                  = 0.3, y == 1
    """

    p_y_x = np.array([[0.1,0.9],[0.7,0.3]])

    """
    Task 1: We wish to determine p(y)
            By the "law of total probability", we have
            p(y == 0) = 0.1 * 0.6 + 0.7 * 0.4 = 0.34
            p(y == 1) = (1 - 0.34) = 0.66

            For doing this the "Monte Carlo" way, we first
            simulate a bunch of 'x's according to p(x)
            and for each x, we simulate a bunch of 'y's according
            to p(y|x). Finally, a histogram should give us the
            desired answer
    """
    x = sample(x_labels, p_x, 10000)
    y = np.array([])
    for i in range(len(x)):
        q = p_y_x[x[i],]
        y = np.append(y, sample(y_labels, q, 10))

    cnt_y = np.array([(y == 0).sum(), (y == 1).sum()])
    p_y = cnt_y/len(y)
    print('p(y) is: [%1.2f, %1.2f]'%(p_y[0], p_y[1]))

    """
    Task 2: Sample from posterior. We wish to determine
            p(x|y); say, p(x == 0 | y = 0), p(x == 1| y = 0)
            This is easily done by applying Bayes' theorem
            p(x|y) = p(y|x) p(x) / sum_x (p(y|x) p(x))
            e.g.:
            p(x == 0| y = 0) = p(y == 0| x = 0) p(x == 0)
                              ----------------------------
              (p(y == 0| x = 0) p(x == 0) + p(y == 0| x = 1) p(x == 1))

              = 0.1 * 0.6 / (0.1 * 0.6 + 0.7 * 0.4) = 0.06 / 0.34 = 0.176

             and,
             p(x == 1| y = 0) = (1 - 0.176) = 0.823

             similarly,
             p(x == 0| y = 1) = 0.9 * 0.6/(0.9 * 0.6 + 0.3 * 0.4) = 0.818
             and
             p(x == 1| y = 1) = (1 - 0.818) = 0.182

             ---------

             To do this the "Monte Carlo" way, we begin by sampling
             x from p(x). Then, for each x, we generate a y based on
             p(y|x).
             Now, choose only those x:s from which y = 1 was generated and
             discard the rest. The new set of x's (the resampled batch)
             would have the distribution p(x|y=1)
    """
    x = sample(x_labels, p_x, 10000)
    x_new = np.array([])
    for i in range(len(x)):
        q = p_y_x[x[i],]
        y = sample(y_labels, q, 1)
        if y == 0:
            x_new = np.append(x_new, x[i])

    #done! now, check the distribution
    cnt_x_new = np.array([(x_new == 0).sum(), (x_new == 1).sum()])
    p_x_y0 = cnt_x_new / len(x_new)
    print('p(x|y=0) is: [%1.2f, %1.2f]'%(p_x_y0[0], p_x_y0[1]))
    print('INFO: len(x) = %d, len(x_new) = %d'%(len(x), len(x_new)))


import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import plotly.graph_objs as go
import statsmodels as sm
from statsmodels.graphics.tsaplots import plot_pacf
from statsmodels.graphics.tsaplots import plot_acf

import warnings
warnings.filterwarnings("ignore")

def acf_pacf_plots(series):
    # Original Time-Series
    fig, axes = plt.subplots(4, 3, figsize=(25,20))
    axes[0, 0].plot(series)
    axes[0, 0].set_title('Raw Data')
    plot_acf(series, ax=axes[0, 1])
    plot_pacf(series, ax=axes[0, 2])

    # 1st Differencing Applied to remove trend
    first_Dif = series.diff(1)
    axes[1, 0].plot(first_Dif[1:])
    axes[1, 0].set_title('1st Order Differencing')
    plot_acf(first_Dif[1:], ax=axes[1, 1])
    plot_pacf(first_Dif[1:], ax=axes[1, 2])

    # Log First Difference to remove variance in the data
    first_Log_Dif = np.log(series).diff(1)
    axes[2, 0].plot(first_Log_Dif[1:])
    axes[2, 0].set_title('Log First Differencing')
    plot_acf(first_Log_Dif[1:], ax=axes[2, 1])
    plot_pacf(first_Log_Dif[1:], ax=axes[2, 2])

    # Seasonal Differencing Applied After 1st Differencing to remove suspected seasonality
    seasonal_Dif = first_Dif.diff(7)
    axes[3, 0].plot(seasonal_Dif[8:])
    axes[3, 0].set_title('Seasonal Differencing After 1st Differencing')
    plot_acf(seasonal_Dif[8:], ax=axes[3, 1])
    plot_pacf(seasonal_Dif[8:], ax=axes[3, 2])

# plots the training set, test set, and the forecasted values for a given time series
def forecast_whole_plot(train, test, forecast, title, xaxis_title, yaxis_title):
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=train.index, y= train.values,
                        mode='lines+markers',
                        name='Training Set'))
    fig.add_trace(go.Scatter(x=test.index, y= test.values,
                        mode='lines+markers',
                        name='Test Set'))
    fig.add_trace(go.Scatter(x=forecast.index, y= forecast.values,
                        mode='lines+markers',
                        name='Forecast'))
    fig.update_layout(title= title,
                       xaxis_title= xaxis_title,
                       yaxis_title= yaxis_title)
    fig.show()

# plot of actual values and values model predicted
def closeup_forecast_plot(test, forecast, plot_title, xaxis_title, yaxis_title):
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=test.index, y= test.values,
                        mode='lines+markers',
                        name='Actual'))
    fig.add_trace(go.Scatter(x=forecast.index, y= forecast.values,
                        mode='lines+markers',
                        name='Forecast'))
    fig.update_layout(title= plot_title,
                       xaxis_title= xaxis_title,
                       yaxis_title= yaxis_title)
    fig.show()

import numpy,import statsmodels,from statsmodels


from sympy import symbols, solve, diff, pprint, integrate
# The 2000 census in a particular area gives us an age distribution that is approximately given (in millions) by the function

multiplier = 1000000

x = symbols( 'x' )
F = 39 + 2.11*x - 0.81*x**2

# where x varies from 0 to 9 decades.
# The population of a given age group can be found by integrating this function over the interval for that age group.

# Find the integral over the interval
a, b = 0, 9
population = round( integrate( F, ( x, a, b ) ) )

# b
print( 'The total number of people in this area aged 0 to 90 was about {0} million in 2000.'.format( population ) )

# Find the number of people alive in 2000 that were born in the seventies, that is, those in the range of 4 to 5 decades in 2000
a, b = 4, 5
round( integrate( F, ( x, a, b ) ) )


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

# # Método de bisección
# J.J
# ---
# Dada una función continua $f$ en un intervalo cerrado $[a,b]$, si $f(a)f(b) < 0$ se puede asegurar que existe un valor $f(p) = 0$ para algún $p \in [a,b]$.
#
# El método consiste en escoger un valor $x_{1}$ en el centro del intervalo: $x_{1} = \frac{1}{2}(a+b)$. Si $f(a)f(x_{1}) < 0$ la raíz se encuentra ahora en el intervalo $[a,x_{1}]$ y se procede con otra iteración ahora en el punto $x_{2} =  \frac{1}{2}(a+x_{1})$. En el caso de que el signo opuesto haya sido con $f(b)$, $f(x_{1})f(b) < 0$, la raíz se encuentra en el intervalo $[x_{1},b]$ y la siguiente iteración es entonces $x_{2} = \frac{1}{2}(x_{1}+b)$.
#
# Siguiendo estos pasos, el método eventualmente disminuye la longitud del intervalo convergiendo a la raíz de la función.

# Ejemplo: $f(x) = 2x^{3} - 9x^{2} + 7x + 6$

# In[1]:

import numpy as np
import matplotlib.pyplot as plt

# In[2]:

def f(x):
    f = 2*x**3 - 9*x**2 + 7*x + 6
    return f

# In[3]:

X = np.linspace(-1, 4, 100)
plt.plot(X,f(X), label = 'f(x)')
plt.legend()
plt.show()

# Hay raices en los intervalos $[-1,0]$, $[1.5, 2.5]$ y $[2.5, 3.5$].

# In[4]:

e = 0.0001 #error

# In[5]:

def Biseccion(a, b, func = f):
    x = (1/2)*(a+b)
    while f(x) > e:
        if f(a)*f(x) < 0:
            Biseccion(a,x)
        elif f(x)*f(b) < 0:
            Biseccion(x,b)
    return x

# In[6]:

sol1 = Biseccion(-1,0)
sol2 = Biseccion(1.5,2.5)
sol3 = Biseccion(2.5,3.5)
print(sol1, sol2, sol3)

# In[7]:

X = np.linspace(-1, 4, 100)
plt.plot(X,f(X), label = 'f(x)')
plt.plot(sol1, f(sol1), 'ro')
plt.plot(sol2, f(sol2), 'ro')
plt.plot(sol3, f(sol3), 'ro')
plt.legend()
plt.show()


"""
Created on Wed Feb 21 10:37:33 2018

@author: William Holtam
"""

import numpy as np
import pandas as pd
import pylab as plt
from scipy.stats import norm, probplot
from matplotlib.ticker import FuncFormatter

headers = ['Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']
dtypes = {'Date': 'str', 'Open': 'float', 'High': 'float', 'Low': 'float', 'Close': 'float', 'Adj Close': 'float', 'Volume': 'int'}
parse_dates = ['Date']
df = pd.read_csv('DNL.L.csv',
                 delimiter=',',
                 header=0,
                 index_col=None,
                 dtype=dtypes,
                 parse_dates=parse_dates)
print(df.info())
adj_close = df.loc[:,"Adj Close"]
adj_close = adj_close.values.tolist()

daily_returns=[0]
for i in np.arange(1,len(adj_close)-1):
    returns = (adj_close[i]-adj_close[i-1])/adj_close[i-1]
    daily_returns.append(returns)
plt.hist(adj_close, bins=30)
plt.show()

mu, std = norm.fit(daily_returns)

print(norm.fit(daily_returns))
print(mu)
fig, axes = plt.subplots(ncols=1, sharey=True)
fig = plt.hist(daily_returns, bins=100, density=True)
axes.xaxis.set_major_formatter(FuncFormatter(lambda x, _: '{:.0%}'.format(x)))
xmin,xmax = plt.xlim()
plt.xlim(xmin,xmax)
x = np.linspace(xmin,xmax,100)
p = norm.pdf(x,mu,std)
plt.plot(x,p,'k',linewidth=2)
title = "Fit results: mu = %.5f,  std = %.3f" % (mu, std)
plt.title(title)
plt.show()

probplot(daily_returns, plot=plt)
plt.xlim(-4,4)
plt.ylim(-0.3,0.15)
plt.show()

five_day_returns = [0]
for i in np.arange(4,len(adj_close)-1,5):
    returns = (adj_close[i]-adj_close[i-1]) / adj_close[i-1]
    five_day_returns.append(returns)

fig, axes = plt.subplots(ncols=1, sharey=True)
fig = plt.hist(five_day_returns, bins=100, density=True)
axes.xaxis.set_major_formatter(FuncFormatter(lambda x, _: '{:.0%}'.format(x)))
xmin,xmax = plt.xlim()
plt.xlim(xmin,xmax)
x = np.linspace(xmin,xmax,100)
p = norm.pdf(x,mu,std)
plt.plot(x,p,'k',linewidth=2)
title = "Fit results: mu = %.5f,  std = %.3f" % (mu, std)
plt.title(title)
plt.show()

probplot(five_day_returns, plot=plt)
plt.xlim(-4,4)
plt.ylim(-0.3,0.15)
plt.show()

ten_day_returns = [0]
for i in np.arange(9,len(adj_close)-1,10):
    returns = (adj_close[i]-adj_close[i-1]) / adj_close[i-1]
    ten_day_returns.append(returns)

fig, axes = plt.subplots(ncols=1, sharey=True)
fig = plt.hist(ten_day_returns, bins=100, density=True)
axes.xaxis.set_major_formatter(FuncFormatter(lambda x, _: '{:.0%}'.format(x)))
xmin,xmax = plt.xlim()
plt.xlim(xmin,xmax)
x = np.linspace(xmin,xmax,100)
p = norm.pdf(x,mu,std)
plt.plot(x,p,'k',linewidth=2)
title = "Fit results: mu = %.5f,  std = %.3f" % (mu, std)
plt.title(title)
plt.show()

probplot(ten_day_returns, plot=plt)
plt.xlim(-4,4)
plt.ylim(-0.3,0.15)
plt.show()

k = 0
number_of_days = [1,4,9]
increment = [1,5,10]
increment_label = ["Daily Returns", "Five Day Returns", "Ten Day Returns"]
list_label = ["daily_returns", "five_day_returns", "ten_day_returns"]
for j in list_label:
    j = [0]
    for i in np.arange(number_of_days[k],len(adj_close)-1,increment[k]):
        returns = (adj_close[i]-adj_close[i-1]) / adj_close[i-1]
        j.append(returns)

    mu, std = norm.fit(j)

    fig, axes = plt.subplots(ncols=1, sharey=True)
    fig = plt.hist(j, bins=100, density=True)
    axes.xaxis.set_major_formatter(FuncFormatter(lambda x, _: '{:.0%}'.format(x)))
    xmin,xmax = plt.xlim()
    plt.xlim(xmin,xmax)
    x = np.linspace(xmin,xmax,100)
    p = norm.pdf(x,mu,std)
    plt.plot(x,p,'k',linewidth=2) # This isn't correct but it's a start
    title = "Fit results: mu = %.5f,  std = %.3f" % (mu, std)
    plt.title(title)
    plt.show()

    probplot(j, plot=plt)
    plt.title("Probability Plot of " + increment_label[k])
    plt.xlim(-4,4)
    plt.ylim(-0.3,0.15)
    plt.show()

    k += 1


import numpy as np
import pytest
from scipy.stats import beta
from ..unlikely.misc import find_closest, hpdi

def test_hpdi():
    size = 10_000
    # Skewed to the right
    array = np.random.beta(2, 1, size=size)
    credible_interval_width = 0.95
    lower, upper = hpdi(proba=credible_interval_width, array=array)
    beta_dist = beta(2, 1)
    probability = beta_dist.cdf(upper) - beta_dist.cdf(lower)
    assert credible_interval_width == pytest.approx(probability, abs=0.01)
    assert 1 == pytest.approx(upper, abs=0.01)

    # Skewed to the left
    array = np.random.beta(1, 2, size=size)
    credible_interval_width = 0.95
    lower, upper = hpdi(proba=credible_interval_width, array=array)
    beta_dist = beta(1, 2)
    probability = beta_dist.cdf(upper) - beta_dist.cdf(lower)
    assert credible_interval_width == pytest.approx(probability, abs=0.01)
    assert 0 == pytest.approx(lower, abs=0.01)

def test_find_closest():
    assert find_closest(
        to_find=1,
        values=[0, 1, 2, 3],
        left_index=0,
        right_index=3
    ) == 1

    # 4.4 is closer to 4 than 5
    assert find_closest(
        to_find=4.4,
        values=[0, 1, 2, 3, 4, 5, 6],
        left_index=0,
        right_index=6
    ) == 4

    # 4.5 is equally close to 4 and 5. Let's pick the bigger value.
    assert find_closest(
        to_find=4.5,
        values=[0, 1, 2, 3, 4, 5, 6],
        left_index=0,
        right_index=6
    ) == 5

    # 4.6 is closer to 5 than 4, so we pick 5.
    assert find_closest(
        to_find=4.6,
        values=[0, 1, 2, 3, 4, 5, 6],
        left_index=0,
        right_index=6
    ) == 5

    # Test out of bounds left
    assert find_closest(
        to_find=-1,
        values=[0, 1, 2, 3, 4, 5, 6],
        left_index=0,
        right_index=6
    ) == 0

    # Test out of bounds right
    assert find_closest(
        to_find=100,
        values=[0, 1, 2, 3, 4, 5, 6],
        left_index=0,
        right_index=6
    ) == 6


from dataclasses import dataclass
from enum import Enum
from typing import List, Callable

import numpy as np

def _line_search(func: Callable, g0: np.ndarray, x0: np.ndarray, s: np.ndarray) -> np.ndarray:
    """Line-search in direction s, starting from x0, with the function func to minimize and g0 the gradient at x0."""
    f0 = func(x0)

    a = 1
    c = 1e-4
    rho = 0.5
    max_iter = 100

    for _ in range(max_iter):
        if func(x0 + a * s) > f0 + c * a * np.dot(g0, s):
            a *= rho
        else:
            return a * s

    return a * s

@dataclass
class Result:
    """Value x that minimizes the function, and path from initial guess to minimum."""
    x: np.ndarray
    path: List[np.ndarray]

class UpdateRule(Enum):
    """Update types that determine how Hessian approximation is computed."""
    DIAGONAL = 1
    SCALED_IDENTITY = 2
    IDENTITY = 3

def optimize(func: Callable,
             grad: Callable,
             x0: np.ndarray,
             update_rule: UpdateRule = UpdateRule.DIAGONAL,
             grad_zero_tol: float = 1e-5,
             eps: float = np.finfo(np.float).eps,
             min_curv: float = 0.01,
             max_curv: float = 1000,
             max_iter: int = 1000,
             verbose: bool = False) -> Result:
    """
    Minimize function value starting with an initial guess.

    Args:
        func: Function to be minimized.
        grad: Gradient of function to be minimized.
        x0: Start value (initial guess).
        update_rule: UpdateRule.DIAGONAL, UpdateRule.SCALED_IDENTITY, or UpdateRule.IDENTITY.
        grad_zero_tol: If gradient norm is below this value, the algorithm terminates.
        eps: Small value that it added to denominator to avoid division by 0.
        min_curv: Hessian values are clipped to [min_curv, max_curv].
        max_curv: Hessian values are clipped to [min_curv, max_curv].
        max_iter: Maximum number of iterations.
        verbose: Print internal state of algorithm.

    Returns:
        An object of type Result containing the value x that minimizes the function and path towards the minimum.
    """
    x = np.asarray(x0)
    path = [x]

    # apply initial step along steepest descent direction
    D = np.ones_like(x)
    g0 = grad(x)
    s = -g0
    s = _line_search(func, g0, x, s)
    x = x + s
    path.append(x)
    g1 = grad(x)

    # iterate
    for iter_ctr in range(max_iter):
        if np.linalg.norm(g1) < grad_zero_tol * max(np.linalg.norm(x), 1):
            break

        if update_rule == UpdateRule.DIAGONAL:
            y = g1 - g0
            b = s.T @ y
            a = (s ** 2).T @ D
            U = ((b - a) / (np.sum(s ** 4) + eps)) * (s ** 2)
            D = D + U

        elif update_rule == UpdateRule.SCALED_IDENTITY:
            y = g1 - g0
            D = (s.T @ y) / (s.T @ s + eps)

        elif update_rule == UpdateRule.IDENTITY:
            D = 1

        # a very crude way to ensure that diag(D) is pos. definite and s is a descent direction
        D = np.clip(D, min_curv, max_curv)

        s = -g1 / D
        s = _line_search(func, g1, x, s)
        x = x + s
        path.append(x)
        g0 = g1
        g1 = grad(x)

        if verbose:
            print(f'i={iter_ctr} | x={x} | s={s} | g1={g1} | D={D}')

    return Result(x, path)


import numpy as np
import torch

def get_fourier_freq(t_max):
    """Returns the frequencies for a vector of length t_max"""
    n_cos = np.ceil((t_max + 1) / 2)  # number of cosine terms (positive freqs)
    n_sin = np.floor((t_max - 1) / 2)  # number of sine terms (negative freqs)
    w_cos = np.arange(0, n_cos)  # cosine freqs
    w_sin = np.arange(-n_sin, 0)  # sine freqs
    w_vec = np.concatenate((w_cos, w_sin), axis=0)
    frequencies = 2 * np.pi / t_max * w_vec

    return frequencies

def real_fft(x, n=None):
    """Performs a real discrete 1D Fourier transform of the columns of x"""

    single_vec = False
    if len(x.shape) == 1:
        single_vec = True
        x = x[:, None]

    if n is None:
        n = x.shape[0]

    x_fft = torch.fft.fft(x, n, dim=0) / np.sqrt(n / 2)
    x_fft[0, :] = x_fft[0, :] / np.sqrt(2)

    if np.mod(n, 2) == 0:
        imx = int(np.ceil((n - 1) / 2))
        x_fft[imx, :] = x_fft[imx, :] / np.sqrt(2)

    x_hat = x_fft.real
    isin = int(np.ceil((n + 1) / 2))
    x_hat[isin:, :] = -x_fft[isin:, :].imag

    if single_vec:
        x_hat = x_hat[:, 0]

    return x_hat

def real_ifft(x_hat, n=None):
    """Performs an inverse of a real discrete 1D Fourier transform of the columns of x"""

    single_vec = False
    if len(x_hat.shape) == 1:
        single_vec = True
        x_hat = x_hat[:, None]

    if n is None:
        n = x_hat.shape[0]

    nxh = x_hat.shape[0]
    n_cos = int(np.ceil((nxh + 1) / 2))
    n_sin = int(np.floor((nxh - 1) / 2))

    x_hat[0, :] = x_hat[0, :] * np.sqrt(2)

    if np.mod(nxh, 2) == 0:
        x_hat[n_cos-1, :] = x_hat[n_cos-1, :] * np.sqrt(2)

    xfft = torch.zeros_like(x_hat, dtype=torch.complex128, device=x_hat.device)
    xfft[:] = x_hat[:]
    xfft[n_cos:, :] = torch.flip(x_hat[1:n_sin+1, :], dims=[0])
    xfft[1:n_sin+1, :] = xfft[1:n_sin+1, :] + 1j * torch.flip(x_hat[n_cos:, :], dims=[0])
    xfft[n_cos:] = xfft[n_cos:, :] - 1j * x_hat[n_cos:, :]

    x = torch.fft.ifft(xfft, dim=0).real * np.sqrt(nxh / 2)
    x = x[:n, :]

    if single_vec:
        x = x[:, 0]

    return x

def get_fourier_basis(n_ind):
    """Returns an orthonormal real Fourier basis for a vector of length n_ind"""

    n_cos = (np.ceil((n_ind + 1) / 2))
    n_sin = (np.floor((n_ind - 1) / 2))

    cos_freq = 2 * np.pi / n_ind * np.arange(n_cos)
    sin_freq = 2 * np.pi / n_ind * np.arange(-n_sin, 0)
    frequency_vec = np.concatenate((cos_freq, sin_freq), axis=0)  # frequency vector

    x = np.arange(n_ind)
    cos_basis = np.cos(cos_freq[:, None] * x[None, :]) / np.sqrt(n_ind / 2)
    sin_basis = np.sin(sin_freq[:, None] * x[None, :]) / np.sqrt(n_ind / 2)
    fourier_basis = np.concatenate((cos_basis, sin_basis), axis=0)

    fourier_basis[0, :] = fourier_basis[0, :] / np.sqrt(2)

    if np.mod(n_ind, 2) == 0:
        fourier_basis[int(n_cos-1), :] = fourier_basis[int(n_cos-1), :] / np.sqrt(2)

    return fourier_basis, frequency_vec


from sympy.abc import _clash1 # So that sympify will be able to convert string to symbolic objects
from statistics import mean # Average method
from math import log10 # Log base 10
from sympy import * # Module to work with symbolic variables
from util import * # Auxiliar function in  util.py

# EXACT METHODS _______________________________________________________________________________________

# GAUSSIAN ELIMINATION METHOD
def gaussianElimination(coeffs: Matrix, result: Matrix, displayConsoleLog = True):
    # Checks if all coefficients are numeric
    try:
        for coef in coeffs: coef = float(coef)
    except:
        raise Exception("The coefficients must be numeric.")

    # Checks the system's order
    if coeffs.shape[0] == coeffs.shape[1] == result.shape[0] and result.shape[1] == 1:
        order = coeffs.shape[0] # System order (number of equations and variables)
        variables = Matrix([f"x{i}" for i in range(order)])
    else:
        raise Exception("The system is invalid.")

    # Checks if it is an independent linear system
    if coeffs.det() != 0 and displayConsoleLog:
        print("___________________________________________________________________________\n\n")
        print(f"GAUSSIAN ELIMINATION METHOD ::: The linear system is an independent one (has only 1 solution).")
    elif coeffs.det() == 0:
        if displayConsoleLog:
            print("___________________________________________________________________________\n\n")
            print(f"GAUSSIAN ELIMINATION METHOD ::: The linear system has either no or infinite solutions.\n")
            print("___________________________________________________________________________\n\n")

        return

    aug = coeffs.col_insert(order + 1, result) # Augmented Matrix

    if displayConsoleLog:
        print(f"GAUSSIAN ELIMINATION METHOD ::: Step no.{'0'.zfill(len(str(order)))}: augmented matrix [A|b] = ")
        pprint(aug)
        print()

    for j in range(order-1): # Columns
        while True:
            # Swaps elements if needed
            if aug.col(j)[j] == 0:
                i = j

                # Selects the first line below the current whose corresponding element isn't 0
                while aug.col(j)[i] == 0 and i < order: i += 1

                # If no line was found, there is no solution
                if aug.col(j)[i] == 0: raise Exception("No solution was found (main diagonal element equals to 0).")

                aug = swapMatrix(aug, "row", i, j)
                if displayConsoleLog:
                    print(f"GAUSSIAN ELIMINATION METHOD ::: Step no.{str(j+1).zfill(len(str(order)))}: swap rows {j+1} and {i+1}.")

            for i in range(j+1, order): # Rows
                multiplier = aug.row(i)[j] / aug.row(j)[j]
                aug = subMatrix(aug, "row", i, aug.row(i) - multiplier*aug.row(j))

                if displayConsoleLog:
                    print(f"GAUSSIAN ELIMINATION METHOD ::: Step no.{str(j+1).zfill(len(str(order)))}: row{i+1} = row{i+1} - ({multiplier}) * row{j+1}.")

            # If there's no need for more swaping, breaks out of the while loop
            if aug.col(j)[j] != 0: break

        if displayConsoleLog:
            print(f"GAUSSIAN ELIMINATION METHOD ::: Step no.{str(j+1).zfill(len(str(order)))}: augmented matrix [A|b] = ")
            pprint(aug)
            print()

    if displayConsoleLog: print("___________________________________________________________________________\n\n")

    result = aug.col(-1).copy()
    coeffs = aug.copy(); coeffs.col_del(-1)

    sol = solve(coeffs*variables - result, variables)
    sol = Matrix([sol.get(var) for var in variables])

    return coeffs, result, sol

# CHOLESKY'S METHOD
def cholesky(coeffs: Matrix, result = None, displayConsoleLog = True):
    # Checks if all coefficients are numeric
    try:
        for coef in coeffs: coef = float(coef)
    except:
        raise Exception("The coefficients must be numeric.")

    # Checks if the coefficient matrix is a square one
    if coeffs.shape[0] == coeffs.shape[1]:
        order = coeffs.shape[0] # System order
    else:
        raise Exception("The system is invalid.")

    # Checks if the result parameter is a matrix
    if result:
        if type(result) is not Matrix: raise Exception("The 'result' parameter must be a matrix.")
        elif result.shape[0] != order or result.shape[1] != 1: raise Exception("The system is invalid.")
        else:
            variables = Matrix([f"x{i}" for i in range(order)])

    # Checks if it is an independent linear system
    if coeffs.det() != 0 and displayConsoleLog:
        print("___________________________________________________________________________\n\n")
        print("CHOLESKY METHOD ::: The linear system is an independent one (has only 1 solution).")
    elif coeffs.det() == 0:
        if displayConsoleLog:
            print("___________________________________________________________________________\n\n")
            print("CHOLESKY METHOD ::: The linear system has either no or infinite solutions.\n")
            print("___________________________________________________________________________\n\n")

        return

    # Checks if the coefficient matrix is simetric
    if coeffs.is_symmetric() and displayConsoleLog:
        print("CHOLESKY METHOD ::: The coefficient matrix is symmetric.")
    elif not coeffs.is_symmetric():
        if displayConsoleLog:
            print("CHOLESKY METHOD ::: The coefficient matrix is not symmetric.")

        return

    G = []
    for i in range(order): # Rows
        row = []

        for j in range(order): # Columns
            if j < i:
                el = (coeffs.row(i)[j] - sum([(row[k])*(G[j][k]) for k in range(j)]))/G[j][j]

            elif j == i:
                el = sqrt(coeffs.row(j)[j] - sum([(row[k])**2 for k in range(j)]))

            else: # j > i
                el = 0

            if displayConsoleLog and j <= i:
                print(f"CHOLESKY METHOD ::: g_{i+1}{j+1} = % .4f." % el)

            row.append(el)

        G.append(row)

    G = Matrix(G)

    if displayConsoleLog:
        print(f"\nCHOLESKY METHOD ::: G = ")
        pprint(G)

        print(f"\nCHOLESKY METHOD ::: G^T = ")
        pprint(G.T)
        print()

        if not result: print("___________________________________________________________________________\n\n")

    # If a result matrix was passed, it is possible to calculate the system's solution
    if result:
        # Solution:
        # A*x = b --> G*G_T*x = b --> G*y = b
        # G_T*x = y

        auxSol = solve(G*variables - result, variables)
        auxSol = Matrix([auxSol.get(var) for var in variables])

        if displayConsoleLog:
            print("CHOLESKY METHOD ::: A*x = b --> G*G^T*x = b --> G*y = b and G^T*x = y.")
            print("CHOLESKY METHOD ::: G*y = b --> y = ")
            pprint(auxSol)
            print()
            print("___________________________________________________________________________\n\n")

        sol = solve(G.T*variables - auxSol, variables)
        sol = Matrix([sol.get(var) for var in variables])

        return G, sol

    else:
        return G, False

# ITERATIVE METHODS _______________________________________________________________________________________

# JACOBI-RICHARDSON'S (JR) METHOD
def jacobiRichardson(coeffs: Matrix, variables: Matrix, result: Matrix, var0: Matrix, epsilon = 10**-6, MAXITER = 100, displayConsoleLog = True, printTotalIterations = True):
    # Try and convert the variables to symbolic
    try:
        for var in variables: var = sympify(var)
    except:
        raise Exception("The entered variables are invalid.")

    # Checks if all coefficients are numeric
    try:
        for coef in coeffs: coef = float(coef)
    except:
        raise Exception("The coefficients must be numeric.")

    # Checks if there are as many variables as equations
    if coeffs.shape[0] == coeffs.shape[1] == variables.shape[0] == result.shape[0]  and variables.shape[1] == result.shape[1] == 1:
        order = coeffs.shape[0] # System order (number of equations and variables)
    else:
        raise Exception("The system is invalid.")

    # Checks if it is an independent linear system
    if coeffs.det() != 0 and displayConsoleLog:
        print("___________________________________________________________________________\n\n")
        print("JACOBI-RICHARDSON METHOD ::: The linear system is an independent one (has only 1 solution).")
    elif coeffs.det() == 0:
        if displayConsoleLog:
            print("___________________________________________________________________________\n\n")
            print("JACOBI-RICHARDSON METHOD ::: The linear system has either no or infinite solutions.\n")
            print("___________________________________________________________________________\n\n")

        return

    if displayConsoleLog: printTotalIterations = True

    L = []
    D = []
    R = []

    for row in range(order):
        rowL = []
        rowD = []
        rowR = []

        for col in range(order):

            if row > col:
                rowL.append(coeffs.row(row)[col])
                rowD.append(0)
                rowR.append(0)

            elif row == col:
                rowL.append(0)
                rowD.append(coeffs.row(row)[col])
                rowR.append(0)

            elif row < col:
                rowL.append(0)
                rowD.append(0)
                rowR.append(coeffs.row(row)[col])

        L.append(rowL)
        D.append(rowD)
        R.append(rowR)

    L = Matrix(L)
    D = Matrix(D)
    R = Matrix(R)

    if displayConsoleLog:
        print("JACOBI-RICHARDSON METHOD ::: Ax = b --> A = L + D + R.")
        print("JACOBI-RICHARDSON METHOD ::: L = ")
        pprint(L)
        print()

        print("JACOBI-RICHARDSON METHOD ::: D = ")
        pprint(D)
        print()

        print("JACOBI-RICHARDSON METHOD ::: R = ")
        pprint(R)
        print()

    if D.det() == 0:
        if displayConsoleLog:
            print("JACOBI-RICHARDSON METHOD ::: As |D| = 0, the method cannot be used.")

        return

    # A x = b --> x_k = B x_(k-1) + g
    B = - D**-1 * (L + R)
    g = D**-1 * result

    if displayConsoleLog:
        print("JACOBI-RICHARDSON METHOD ::: A x = b --> x_k = B x_(k-1) + g.")
        print("JACOBI-RICHARDSON METHOD ::: B = ")
        pprint(B)
        print()

        print("JACOBI-RICHARDSON METHOD ::: g = ")
        pprint(g)
        print()

    # Checks convergence
    if B.norm(oo) < 1:
        if displayConsoleLog:
            print(f"JACOBI-RICHARDSON METHOD ::: ||B||_oo = {B.norm(oo)} < 1, so there is guarantee of convergence.")
    elif B.norm(1) < 1:
        if displayConsoleLog:
            print(f"JACOBI-RICHARDSON METHOD ::: ||B||_1 = {B.norm(1)} < 1, so there is guarantee of convergence.")
    elif B.norm(2) < 1:
        if displayConsoleLog:
            print(f"JACOBI-RICHARDSON METHOD ::: ||B||_2 = {B.norm(2)} < 1, so there is guarantee of convergence.")
    else:
        if displayConsoleLog:
            print(f"JACOBI-RICHARDSON METHOD ::: ||B||_oo = {B.norm(oo)}, ||B||_1 = {B.norm(1)}, ||B||_2 = {B.norm(2)} are all > 1, so there is no guarantee of convergence.")
    if displayConsoleLog: print()

    # No. of decimal places for the results
    precision = int(abs(log10(epsilon))) if epsilon > 0 and log10(epsilon).is_integer else int(6)

    # Only 2 elements will be used, [0] for even iterations and [1] for odd ones
    x = [var0, 0]

    if displayConsoleLog:
        print("JACOBI-RICHARDSON METHOD ::: Iteration no.00: ", end="")
        for i in range(len(variables)): print(f"{variables[i]} = % .{precision}f" % x[0][i], end = ", ")
        print("||vars_k - vars_(k-1)||_oo =  " + "-"*(precision + 2))

    # Auxiliar index variable correspondent to the current element in the 'x' list
    j = 0

    # Loops through the algorithm until the maximum number of iterations is met
    for i in range(1, MAXITER):
        j += 1 if j < 1 else -j # j = 0 --> j = 1 --> j = 0 --> j = 1 ...

        x[j] = B*x[j-1] + g

        if displayConsoleLog:
            print(f"JACOBI-RICHARDSON METHOD ::: Iteration no.{str(i).zfill(2)}: ", end="")
            for k in range(len(variables)): print(f"{variables[k]} = % .{precision}f" % x[j][k], end = ", ")
            print(f"||vars_k - vars_(k-1)||_oo = % .{precision}f" % (x[j] - x[j-1]).norm(oo))

        if( (x[j] - x[j-1]).norm(oo) < epsilon * max(1, x[j].norm(oo)) ):
            if printTotalIterations:
                print("___________________________________________________________________________\n\n") if not displayConsoleLog else print("\n")
                print(f"JACOBI-RICHARDSON METHOD ::: The solution was successfully found after {i+1} iterations.\n")
                print("___________________________________________________________________________\n\n")

            return x[j], int(precision)

    # If it manages to get out of the loop, it means no root was found, so it prints an error message
    raise Exception("The maximum number of iterations was met and no root was found.")

# GAUSS-SEIDEL'S (GS) METHOD
def gaussSeidel(coeffs: Matrix, variables: Matrix, result: Matrix, var0: Matrix, epsilon = 10**-6, MAXITER = 100, displayConsoleLog = True, printTotalIterations = True):
    # Try and convert the variables to symbolic
    try:
        for var in variables: var = sympify(var)
    except:
        raise Exception("The entered variables are invalid.")

    # Checks if all coefficients are numeric
    try:
        for coef in coeffs: coef = float(coef)
    except:
        raise Exception("The coefficients must be numeric.")

    # Checks if there are as many variables as equations
    if coeffs.shape[0] == coeffs.shape[1] == variables.shape[0] == result.shape[0]  and variables.shape[1] == result.shape[1] == 1:
        order = coeffs.shape[0] # System order (number of equations and variables)
    else:
        raise Exception("The system is invalid.")

    # Checks if it is an independent linear system
    if coeffs.det() != 0 and displayConsoleLog:
        print("___________________________________________________________________________\n\n")
        print("GAUSS-SEIDEL METHOD ::: The linear system is an independent one (has only 1 solution).")
    elif coeffs.det() == 0:
        if displayConsoleLog:
            print("___________________________________________________________________________\n\n")
            print("GAUSS-SEIDEL METHOD ::: The linear system has either no or infinite solutions.\n")
            print("___________________________________________________________________________\n\n")

        return

    if displayConsoleLog: printTotalIterations = True

    L = []
    D = []
    R = []

    for row in range(order):
        rowL = []
        rowD = []
        rowR = []

        for col in range(order):

            if row > col:
                rowL.append(coeffs.row(row)[col])
                rowD.append(0)
                rowR.append(0)

            elif row == col:
                rowL.append(0)
                rowD.append(coeffs.row(row)[col])
                rowR.append(0)

            elif row < col:
                rowL.append(0)
                rowD.append(0)
                rowR.append(coeffs.row(row)[col])

        L.append(rowL)
        D.append(rowD)
        R.append(rowR)

    L = Matrix(L)
    D = Matrix(D)
    R = Matrix(R)

    if displayConsoleLog:
        print("GAUSS-SEIDEL METHOD ::: Ax = b --> A = L + D + R.")
        print("GAUSS-SEIDEL METHOD ::: L = ")
        pprint(L)
        print()

        print("GAUSS-SEIDEL METHOD ::: D = ")
        pprint(D)
        print()

        print("GAUSS-SEIDEL METHOD ::: R = ")
        pprint(R)
        print()

    if D.det() == 0:
        if displayConsoleLog:
            print("GAUSS-SEIDEL METHOD ::: As |D| = 0, the method cannot be used.")

        return

    # A x = b --> x_k = B x_(k-1) + g
    B = - (D**-1 * L + eye(order))**-1 * (D**-1 * R)
    g = (D**-1 * L + eye(order))**-1 * (D**-1 * result)

    if displayConsoleLog:
        print("GAUSS-SEIDEL METHOD ::: A x = b --> x_k = B x_(k-1) + g.")
        print("GAUSS-SEIDEL METHOD ::: B = ")
        pprint(B)
        print()

        print("GAUSS-SEIDEL METHOD ::: g = ")
        pprint(g)
        print()

    # Checks convergence
    if B.norm(oo) < 1:
        if displayConsoleLog:
            print(f"GAUSS-SEIDEL METHOD ::: ||B||_oo = {B.norm(oo)} < 1, so there is guarantee of convergence.")
    elif B.norm(1) < 1:
        if displayConsoleLog:
            print(f"GAUSS-SEIDEL METHOD ::: ||B||_1 = {B.norm(1)} < 1, so there is guarantee of convergence.")
    elif B.norm(2) < 1:
        if displayConsoleLog:
            print(f"GAUSS-SEIDEL METHOD ::: ||B||_2 = {B.norm(2)} < 1, so there is guarantee of convergence.")
    else:
        if displayConsoleLog:
            print(f"GAUSS-SEIDEL METHOD ::: ||B||_oo = {B.norm(oo)}, ||B||_1 = {B.norm(1)}, ||B||_2 = {B.norm(2)} are all > 1, so there is no guarantee of convergence.")
    if displayConsoleLog: print()

    # No. of decimal places for the results
    precision = int(abs(log10(epsilon))) if epsilon > 0 and log10(epsilon).is_integer else int(6)

    # Only 2 elements will be used, [0] for even iterations and [1] for odd ones
    x = [var0, 0]

    if displayConsoleLog:
        print("GAUSS-SEIDEL METHOD ::: Iteration no.00: ", end="")
        for i in range(len(variables)): print(f"{variables[i]} = % .{precision}f" % x[0][i], end = ", ")
        print("||vars_k - vars_(k-1)||_oo =  " + "-"*(precision + 2))

    # Auxiliar index variable correspondent to the current element in the 'x' list
    j = 0

    # Loops through the algorithm until the maximum number of iterations is met
    for i in range(1, MAXITER):
        j += 1 if j < 1 else -j # j = 0 --> j = 1 --> j = 0 --> j = 1 ...

        x[j] = B*x[j-1] + g

        if displayConsoleLog:
            print(f"GAUSS-SEIDEL METHOD ::: Iteration no.{str(i).zfill(2)}: ", end="")
            for k in range(len(variables)): print(f"{variables[k]} = % .{precision}f" % x[j][k], end = ", ")
            print(f"||vars_k - vars_(k-1)||_oo = % .{precision}f" % (x[j] - x[j-1]).norm(oo))

        if (x[j] - x[j-1]).norm(oo) < epsilon * max(1, x[j].norm(oo)) :
            if printTotalIterations:
                print("___________________________________________________________________________\n\n") if not displayConsoleLog else print("\n")
                print(f"GAUSS-SEIDEL METHOD ::: The solution was successfully found after {i+1} iterations.\n")
                print("___________________________________________________________________________\n\n")

            return x[j], int(precision)

    # If it manages to get out of the loop, it means no root was found, so it prints an error message
    raise Exception("The maximum number of iterations was met and no root was found.")

if __name__ == "__main__":
    # A = Matrix([ [2, 1, 2], [1, 2, 1], [1, 1, 2] ])
    # # A = Matrix([ [1,2,0], [1,2,1], [0,1,1] ])
    # var = Matrix([ 'x', 'y', 'z' ])
    # res = Matrix([ 1, 2, 3 ])
    # # res = Matrix([ 3, 1, -4 ])

    # coeffs, result, sol = gaussianElimination(A, var, res)

    # print("\n")
    # pprint(coeffs)
    # print()
    # pprint(result)
    # print()
    # pprint(sol)
    # print()

    # # A = Matrix([ [4,2,2], [2,2,1], [2,1,2] ])
    # A = Matrix([ [4, 12, -16], [12, 37, -43], [-16, -43, 98] ])
    # var = Matrix([ 'x', 'y', 'z' ])

    # G = cholesky(A, var)

    A = Matrix([ [10,2,1], [1,5,1], [2,3,10] ])
    var = Matrix([ 'x', 'y', 'z' ])
    res = Matrix([ 7, -8, 6 ])
    var0 = Matrix([7/10, -8/5, 8/5])

    sol, precision = jacobiRichardson(A, var, res, var0, epsilon=10**-5)

    for k in range(len(var)):
        print(f"{var[k]} = % .{precision}f" % sol[k], end = "")
        if k < len(var) - 1: print(", ", end="")
        else: print("\n")

    sol, precision = gaussSeidel(A, var, res, var0, epsilon=10**-5)

    for k in range(len(var)):
        print(f"{var[k]} = % .{precision}f" % sol[k], end = "")
        if k < len(var) - 1: print(", ", end="")
        else: print("\n")


# -*- coding: utf-8 -*-
"""
Created on Thu Dec  2 20:34:32 2021

@author: Oliver
"""
"""Multivariate Optimization"""
import matplotlib.pyplot as plt
import numpy as np
import math
import sys
print(f'sys executable {sys.executable}')
"""Steepest Ascent"""
def HOpt(F,dFx,dFy,x,y):
    import sympy as sym
    from sympy import symbols, solve
    import matplotlib.pyplot as plt

    hsym = symbols('hsym')

    xlist = []
    ylist = []
    flist = []
    dfxlist = []
    dfylist = []

    for i in range(0, 10, 1):
        xold = x
        yold = y

        dfx = dFx(x)
        dfy = dFy(y)

        #Create a function for the path to the top of the mountain.
        g = F(x+dfx*hsym, y+dfy*hsym)
        hexpr = sym.diff(g, hsym)

        hsolved = solve(hexpr)
        hopt = hsolved[0]

        x = xold + hopt*dfx
        y = yold + hopt*dfy

        Fxy = F(x, y)
        dfx = dFx(x)
        dfy = dFy(y)

        xlist.append(x)
        ylist.append(y)
        flist.append(Fxy)
        dfxlist.append(dfx)
        dfylist.append(dfy)

        if dfx <= 0.0001 and dfy <= 0.0001:
            break

    print(x, y, Fxy, dfx, dfy)

def F(x,y):
    return 2*x*y+2*x-x**2-2*y**2

def dFx(x):
    return 2*y+2-2*x

def dFy(y):
    return 2*x-4*y

x = 1.
y = 1.
print(HOpt(F, dFx, dFy, x, y))

# ADD MICROSOFT EXCEL HERE

"""Constrained Optimization using Laplace Multiplier"""

cost_social = 25
cost_tv = 250
budget = 2500

#lets get the minimum and maximum number of campaigns:
social_min = 0
social_max = budget / cost_social

tv_min = 0
tv_max = budget / cost_tv

# if we fix the number of tv campaings, we know the number of social campaigns left to buy by inverting the formula
def n_social(n_tv, budget):
    return (budget - 250 * n_tv) / 25

# if we fix the number of social campaings, we know the number of tv campaigns left to buy by inverting the formula
def n_tv(n_social, budget):
    return (budget - 25 * n_social) / 250

social_x = np.linspace(social_min, social_max, 100)
tv_y = n_tv(social_x, budget)

plt.figure(figsize=(10,5))
plt.plot(social_x, tv_y)
plt.xlabel('Number of social campaigns')
plt.ylabel('Number of tv campaigns')
plt.title('Possible ways of spending the budget')
plt.show()

def revenues(social, tv):
    return social**(3/4) * tv**(1/4) * 7

from mpl_toolkits.mplot3d import Axes3D
social_axis = np.linspace(social_min, social_max, 100)
tv_axis = np.linspace(tv_min, tv_max, 100)
social_grid, tv_grid = np.meshgrid(social_axis, tv_axis)

fig = plt.figure()
ax = fig.gca(projection='3d')

ax.plot_surface(tv_grid, social_grid, revenues(social_grid, tv_grid))

ax.plot(tv_y, social_x, linewidth = 5, color = 'r')

ax.set_xlabel('Number of hours bought')
ax.set_ylabel('Number of materials bought')
ax.set_title('Possible ways of spending the budget')
plt.show()

fig, (ax_l, ax_r) = plt.subplots(1, 2, figsize = (15, 5))

social_axis = np.linspace(social_min, social_max, 100)
tv_axis = np.linspace(tv_max, tv_min, 100)
social_grid, tv_grid = np.meshgrid(social_axis, tv_axis)

im = ax_l.imshow(revenues(social_grid, tv_grid), aspect = 'auto', extent=[social_min, social_max, tv_min, tv_max])
ax_l.plot(social_axis, n_tv(social_axis, 2500), 'r')
ax_l.set_xlabel('Number of social campaigns bought')
ax_l.set_ylabel('Number of tv campaigns bought')
ax_l.set_title('Possible ways of spending the budget')

# The contours are showing how the intersection looks like

social_axis = np.linspace(social_min, social_max)
tv_axis = np.linspace(tv_min, tv_max)
social_grid, tv_grid = np.meshgrid(social_axis, tv_axis)

im2 = ax_r.contour(revenues(social_grid,tv_grid), extent=[social_min, social_max, tv_min, tv_max])
ax_r.plot(social_axis, n_tv(social_axis, 2500), 'r')
ax_r.set_xlabel('Number of social campaings bought')
ax_r.set_ylabel('Number of tv campaigns bought')
ax_r.set_title('Possible ways of spending the budget')

plt.colorbar(im,ax=ax_l)
plt.colorbar(im2,ax=ax_r)

plt.show()

from sympy import *

s, t, l = symbols('s t l')

solve([Eq((21/4)*((t**(1/4))/s**(1/4)) - 25*l, 0),
   Eq((7/4)*(s**(3/4)/t**(3/4)) - 250*l, 0),
   Eq(25*s+250*t - 2500, 0)], [s,t,l], simplify=False)

revenues(75, 2.5)

"""DOES NOT WORK PAST THIS LINE"""
# Lagrange multiplier from the lecture slides:
import numpy as np
from scipy.optimize import minimize
def objective(X):
    x, y = X
    return (x-5)**2 + (y-8)**2
#This is the constraint function that has lambda as a coefficient.
def eq(X):
    x, y = X
    return x*y - 5

def F(L):
    'Augmented Lagrange function'
    x, y, _lambda = L
    return objective([x, y]) + _lambda * eq([x, y])

from autograd import grad
import autograd.numpy as np

# Gradients of the Lagrange function
dfdL = grad(F, 0)

# Find L that returns all zeros in this function.
def obj(L):
    x, y, _lambda = L
    dFdx, dFdy, dFdlam = dfdL(L)
    return [dFdx, dFdy, eq([x, y])]
from scipy.optimize import fsolve
x, y, _lam = fsolve(obj, [0.0, 0.0, 0.0])
print(f'The answer is at {x, y}')

# solving an economic problem
def objective(X):
    x, y = X
    return (160*x**0.66)*(y**0.33)
#This is the constraint function that has lambda as a coefficient.
def eq(X):
    x, y = X
    return 20*x + 0.15*y - 20000.

def G(L):
    'Augmented Lagrange function'
    x, y, _lambda = L
    return objective([x, y]) + _lambda * eq([x, y])

# Gradients of the Lagrange function
dfdL = grad(G, 0)
# Find L that returns all zeros in this function.
def obj(L):
    x, y, _lambda = L
    dFdx, dFdy, dFdlam = dfdL(L)
    return [dFdx, dFdy, eq([x, y])]
from scipy.optimize import fsolve
x, y, _lam = fsolve(obj, [1., 1., 1.0])
print(f'The answer is at {x, y}')

import numpy,from scipy,import sympy,from sympy


import numpy

def compute_weights(H, y):
    '''
    This function compute the regression coefficient using the Closed-form solution.

    Args:
        H: |NxD| Matrix of N observations with D features each
        y: |N| Vector of labels

    Returns:
        W: |D| Vector of features
    '''
    if not type(H) == numpy.matrix:
        H = numpy.matrix(H)
        if H.shape[0] == 1:  # In case there was one feature
            H = H.T
    if not type(y) == numpy.matrix:
        y = numpy.matrix(y).T
    elif y.shape[0] == 1:  # creating a column vector
        y = y.T
    H = add_bias_feature(H)
    first_part = (H.T * H)

    if not (first_part.shape[0] == first_part.shape[1] and numpy.linalg.matrix_rank(first_part) == first_part.shape[0]):
        raise Exception("'H.T x H' is not invertible!")

    W = numpy.linalg.inv(first_part) * H.T * y
    return W

def add_bias_feature(H):
    '''
    Adds an all 1 feature to serve as the bias feature for W[0]

    Args:
        H: |NxD| Matrix of N observations with D features each

    Returns:
        H: |Nx(D+1)| numpy matrix
    '''
    if not type(H) == numpy.matrix:
        H = numpy.matrix(H)
        if H.shape[0] == 1:  # In case there was one feature
            H = H.T
    return numpy.hstack((numpy.ones((H.shape[0], 1)), H))

def predict(H, W):
    '''
    Predicts for H the labels, based on W
    Args:
        H: |NxD| Matrix of N observations with D features each
        W: |D| Vector of features

    Returns:
        y: |N| Vector of predicted labels
    '''
    H = add_bias_feature(H)
    if not type(W) == numpy.matrix:
        W = numpy.matrix(W).T
    elif W.shape[0] == 1:  # creating a column vector
        W = W.T
    y = H * W
    return y

def RSS(y, H, W):
    '''
    Computes the residual sum of squares.
    Args:
        y: |N| vector of true labels
        H: |NxD| Matrix of N observations with D features each
        W: |D| Vector of features

    Returns:
        y: |N| Vector of labels
    '''
    if not type(y) == numpy.matrix:
        y = numpy.matrix(y).T
    elif y.shape[0] == 1:  # creating a column vector
        y = y.T

    E = y - predict(H, W)
    return (E * E.T).sum()


"""Simple implementation of Linear regression model
   with gradiant descent optimisation.
"""
import numpy as np

class LinearRegression():
    """Finds a line which best fits the training data
    of a single i/p variable.
    y = a + bX
    we find the best value of a and b applying gradiant
    descent on a cost function given by
    J(a, b) = (Y - a + bX)^2

    Gradiant Descent:
    a = a - alpha * delta(J(a, b))
    b = b - alpha * delta(J(a, b))
    while keeping b and a constant respectively.

    Parameters
    ==========

    alpha: float, learning rate
       Learning rate in float.
    stopping_condition: int, defaults to 5.
       int indicates the time to stop. 1<t<10
    """

    def __init__(self, alpha, stopping_condition = 5):
        self.alpha = alpha
        self.stopping_condition = stopping_condition

    def fit(self, X, y):
        """
        Fit the data on the linear regression model, i.e fit a line
        in the given data.

        Parameters
        ==========
        X : array of shape (n_samples)
          Independent input data.
        y : array of shape (n_samples)
          Output data, y = f(X)
        """
        X = np.asarray(X)
        if X.ndim != 1:
            raise ValueError("Only single feature input is supported")

        # initialise theta as zero.
        self.theta = [0, 0]
        if self.stopping_condition > 10:
            raise ValueError("Time should be less than 10 secs")

        self._gradiant_descent(X, y)

    def transform(self, X):
        """
        Transform X to f(x) given the linear regression parameters.

        Parameters
        ==========
        X : array of shape(n_samples)
          The input
        """
        X = np.asarray(X)
        if X.ndim != 1:
            raise ValueError("Only single feature input is supported")

        X *= self.theta[1]
        X += self.theta[0]
        return X

    def _gradiant_descent(self, X, y):
        """
        Gradiant descent optmization method to minimise cost function with
        parameters theta0 and theta1.

        Parameters
        ==========
        X : array of shape(n_samples)
          The input
        y : array of shape(n_samples)
          The output, y = f(x)
        """
        import time

        start = time.time()
        N = len(X)
        while True:
            delta_theta0 = sum(((2.0 / N) * (-y + (self.theta[0] + (self.theta[1] * X)))))
            delta_theta1 = sum(((2.0 / N) * X * (-y + (self.theta[0] + (self.theta[1] * X)))))
            self.theta[0] = self.theta[0] - self.alpha  * delta_theta0
            self.theta[1] = self.theta[1] - self.alpha  * delta_theta1
            if (time.time() - start >= self.stopping_condition):
                break

    def plot(self, X, y, y_pred):
        """
        Plot graph of X, real y and predicted y as a line

        Parameters
        ==========
        X : array, 1-D
         The input feature vector
        y : array, 1-D
         The output
        """

        from matplotlib import pyplot as plt

        plt.plot(X, y, 'rs', X, y_pred)
        plt.show()


from numpy import *
import numpy as np
# from numba import jit

# @jit

def detrending_coeff(win_len , order):

#win_len = 51
#order = 2
	n = (win_len-1)/2
	A = mat(ones((win_len,order+1)))
	x = np.arange(-n , n+1)
	for j in range(0 , order + 1):
		A[:,j] = mat(x ** j).T

	coeff_output = (A.T * A).I * A.T
	return coeff_output , A

# coeff_output,A = detrending_coeff(5,2)
# print(coeff_output)
# print(A)