# Lagrange interpolating polynomial

import numpy as np

def lagrange(x, y, X):
    '''
    Lagrange interpolation method
    Input:
      - x : precomputed points x axis values
      - y : precomputed points y axis values
      - X : query points x axis
    Output:
      - Y : query points y axis (interpolation results)
    '''
    n = len(x)
    Y = np.zeros(len(X))
    for i in range(n):
        L = np.ones(len(X))
        for j in range(n):
            if i != j:
                L *= (X - x[j])/(x[i]-x[j])
        Y += y[i]*L
    return Y

def lagrange_poly(x, y):
    '''
    Return a Lagrange interpolating polynomial.

    Input
      - x : array-like x-coordinates of a set of datapoints.
      - y : array-like y-coordinates of a set of datapoints, i.e. f(x).
    Output:
      - p: Lagrange interpolating polynomial as a `numpy.poly1d` instance.
    '''
    n = len(x)
    p = np.poly1d(0.0)
    for j in range(n):
        pt = np.poly1d(y[j])
        for k in range(n):
            if k != j:
                pt *= np.poly1d([1.0, -x[k]])/(x[j]-x[k])
        p += pt
    return p


# -*- coding: utf-8 -*-
#
# Copyright 2018 Amir Hadifar. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np

# our data
X_raw = np.array([2013, 2014, 2015, 2016, 2017], dtype=np.float32)
y_raw = np.array([12000, 14000, 15000, 16500, 17500], dtype=np.float32)
# normilize our data
X = (X_raw - X_raw.min()) / (X_raw.max() - X_raw.min())
y = (y_raw - y_raw.min()) / (y_raw.max() - y_raw.min())

# initilization
a, b = 0, 0
# number of iteration
num_epoch = 10000
learning_rate = 1e-3
# our main loop
for e in range(num_epoch):
    y_pred = a * X + b  # our line equation
    grad_a = (y_pred - y).dot(X)  # calculate gradient for a
    grad_b = (y_pred - y).sum()  # calculate gradient for b
    # Update parameters.
    a = a - learning_rate * grad_a
    b = b - learning_rate * grad_b
print(a, b)


# Numerical differentiation error test.
#
# Finite Forward Difference (FFD) and Symmetric Differential Quotient (SDQ)
# errors magnitude are compared for a known function on a fixed point x0.
#
# The test also shows that, after a certain threshold, both the methods are
# heavily affected by numerical cancellation.
#
# Note that the SDQ method produces smaller errors.

import matplotlib.pyplot as plt
import numpy as np

a = 0.5
ddx = np.cos(a)
# Get 100 numbers between 10^-17 and 10^0 evenly spaced on a log scale.
hh = np.logspace(-17, 0, 100, endpoint=True)
ffd = []
sdq = []
ffd_err = []
sdq_err = []
for h in hh:
    val = (np.sin(a+h)-np.sin(a))/h
    ffd.append(val)
    ffd_err.append(abs(ddx - val))
    val = (np.sin(a+h)-np.sin(a-h))/(2*h)
    sdq.append(val)
    sdq_err.append(abs(ddx - val))
# Print a tabular form of the results
print('ddx = {}'.format(ddx))
print('| h                     | FFD         | Err         | SDQ         | Err')
for h,a,b,c,d in zip(hh,ffd, ffd_err, sdq, sdq_err):
    print('| {:.19f} | {:.9f} | {:.9f} | {:.9f} | {:.9f}'.format(h,a,b,c,d))
# Plot the graph
plt.loglog(hh, ffd_err)
plt.loglog(hh, sdq_err)
plt.grid(True)
plt.xlabel('h')
plt.ylabel('error')
plt.legend(['ffd','sdq'])
plt.show()


import numpy as np
import pandas as pd
from numpy.linalg import inv, norm

def normalize(X):
    mu = X.mean(0)
    std = X.std(0)
    return (X - mu) / std, mu, std

def add_bias_column(X):
    ones = np.ones(X.shape[0]).reshape(X.shape[0], 1)
    return np.concatenate((ones, X), 1)

def normal_equation(X, y):
    w = inv(X.T.dot(X)).dot(X.T).dot(y)
    return w

def compute_error(X, y, w):
    return (y - X.dot(w)).T.dot(y - X.dot(w))

def gradient_descent(X, y, alpha=0.001, epsilon=0.0001):
    m, n = X.shape
    w_old = np.random.rand(n).reshape(n, 1)
    w_new = np.zeros((n, 1))
    i = 0
    while norm(w_new - w_old)  > epsilon:
        w_old = w_new
        RSS = compute_error(X, y, w_old)
        grad_RSS = (X.T.dot(X).dot(w_old) - X.T.dot(y))
        w_new = w_old - alpha * grad_RSS
        print(i, np.squeeze(RSS))
        i += 1
    return w_new

if __name__ == '__main__':
    train_data = pd.read_csv('kc_house_train_data.csv')
    test_data = pd.read_csv('kc_house_test_data.csv')

    features = ['bedrooms', 'bathrooms', 'sqft_living', 'sqft_lot']
    target = 'price'

    X = train_data[features].values
    y = train_data[target].values.reshape(X.shape[0], 1)

    X, mu, std = normalize(X)
    X = add_bias_column(X)

    ne_w = normal_equation(X, y)
    gd_w = gradient_descent(X, y, alpha=0.00001)

    print('-'*70)
    print('Normal Equation Model:\n', ne_w, '\n', '-'*70)
    print('Gradient Descent Model:\n', gd_w, '\n', '-'*70)
    print('Difference between them:\n', ne_w - gd_w, '\n', '-'*70)


###_______________________________ SymPy ___________________________________###
# SymPy es una biblioteca de Python para matemática simbólica. Apunta a convertirse
# en un sistema de algebra computacional (CAS) con todas sus prestaciones manteniendo
# el código tan simple como sea posible para manterlo comprensible y fácilmente extensible.
# SymPy está escrito totalmente en Python y no requiere bibliotecas adicionales.
# Este proyecto comenzó en 2005, fue lanzado al público en 2007 y a él han contribuido
# durante estos años cientos de personas.

##_________________________ Creating Symbols ___________________________##
from sympy import init_session, Symbol, symbols, pi, I, E, cos, sin, exp, tan, simplify, expand, factor, collect, apart, cancel, expand_trig, diff, Derivative, Function, integrate, limit, series, Eq, solve, dsolve, Matrix, N
init_session(use_latex='matplotlib')

# Creamos el símbolo a
a = Symbol('a')
print((a + pi) ** 2)
print(type(a))
b = 2 * a
print(type(b))
print(b)
# Como Python permite que el tipo de una variable cambie, si ahora le asigno a
# a un valor float deja de ser un símbolo
a = 2.26492
print(type(a))

# __Las conclusiones son:_
    # * __Si quiero usar una variable como símbolo debo crearla previamente.__
    # * Las operaciones con símbolos devuelven símbolos.
    # * Si una varibale que almacenaba un símbolo recibe otra asignación, cambia de tipo.
# Las variables de tipo Symbol actúan como contenedores en los que no sabemos qué
# hay (un real, un complejo, una lista...). Hay que tener en cuenta que: una cosa es
# el nombre de la variable y otra el símbolo con el que se representa
#creación de símbolos
coef_traccion = Symbol('c_T')
# Diferencia entre variable y símbolo
a = symbols('b')
x, y, z, t = symbols('x y z t')
#  símbolos griegos:
w = symbols('omega')
W = symbols('Omega')

# Por defecto, SymPy entiende que los símbolos son números complejos. Esto puede
# producir resultados inesperados ante determinadas operaciones como, por ejemplo,
# lo logaritmos. Podemos indicar que la variable es real, entera... en el momento
# de la creación

# Creamos símbolos reales
x, y, z, t = symbols('x y z t', real=True)
# Podemos ver las asunciones de un símbolo
print(x.assumptions0)

##_________________________ Expressions ___________________________##
expr = cos(x)**2 + sin(x)**2
print(expr)

# Podemos pedirle que simplifique la expresión anterior
print(simplify(expr))

# En algunas ocasiones necesitaremos sustituir una variable por otra, por otra
# expresión o por un valor, using 'subs'
# Sustituimos x por y ** 2
print(expr.subs(x, y**2))
# ¡Pero la expresión no cambia!
# Para que cambie
expr = expr.subs(sin(x), exp(x))
print(expr)
# Particulariza la expresión sin(x)+3x en x=pi
print((sin(x) + 3 * x).subs(x, pi))

# Aunque si lo que queremos es obtener el valor numérico lo mejor es .evalf()
print((sin(x) + 3 * x).subs(x, pi).evalf(25)) # Nº of decimal places is the n isnide ()

# el mismo resultado se obtiene ocn la función N()
print(N(pi,25))

##_________________________ Simplification ___________________________##
# SymPy ofrece numerosas funciones para __simplificar y manipular expresiones__.
 # Entre otras, destacan:
    # * `expand()`
    # * `factor()`
    # * `collect()`
    # * `apart()`
    # * `cancel()`
# Puedes consultar en la documentación de SymPy lo que hace cada una y algunos ejemplos.
# Existen también funciones específicas de simplificación para funciones trigonométricas,
# potencias y logaritmos. Abre [esta documentación](http://docs.sympy.org/latest/tutorial/simplification.html) si lo necesitas.

#_____________ Examples _____________##
expr1 = (x**3 + 3*y +2)**2
print(expand(expr1))

expr2 = (3*x**2 - 2*x +1) / ((x-1)**2)
print(apart(expr2))

expr3 = (x**3 + 9*x**2 +27*x + 27)
print(factor(expr3))

expr4 = sin(x + 2 * y)
print(expand_trig(expr4))
print(expand(expr4, trig=True))

##_____________ Derivatives _____________##
# Puedes derivar una expresion usando el método .diff() y la función dif()
#creamos una expresión
expr = cos(x)
#obtenemos la derivada primera con funcion
print(diff(expr, x)) #or:
print(expr.diff(x))

# Third derivative?
print(expr.diff(x,x,x))
print(expr.diff(x,3))

# Many derivatives?
expr_xy = y ** 3 * sin(x) ** 2 + x ** 2 * cos(y)
print(diff(expr_xy, x, 2, y, 2))

# Si queremos que la deje indicada, usamos Derivative()
print(Derivative(expr_xy, x, 2, y))

# Can SymPy apply the chain rule?
# Creamos una función F #
F = Function('F')
print(F(x))
# Creamos una función G
G = Function('G')
print(G(x))
# Derivamos la función compuesta F(G(x))
print(F(G(x)).diff(x))

# If we know the functions: #
# definimos una f
f = 2 * y * exp(x)
# definimos una g(f)
g = f **2 * cos(x) + f
#la derivamos
print(diff(g,x))

##_____________ Integrals _____________##
int1 = cos(x)**2
print(int1.integrate(x))

int2 = 1 / sin(x)
print(int2.integrate(x))

x, a = symbols('x a', real=True)

int3 = 1 / (x**2 + a**2)**2
print(int3.integrate(x))

##_________________________ Limits ___________________________##
# Calculemos este límite sacado del libro _Cálculo: definiciones, teoremas y
# resultados_, de Juan de Burgos:
    # $$\lim_{x \to 0} \left(\frac{x}{\tan{\left (x \right )}}\right)^{\frac{1}{x^{2}}}$$
# Primero creamos la expresión:
x = symbols('x', real=True)
expr = (x / tan(x)) ** (1 / x**2)
#Obtenemos el límite con la función limit() y si queremos dejarlo indicado,
# podemos usar Limit():
print(limit(expr, x, 0))

##_________________________ Series ___________________________##
#creamos la expresión
expr = exp(x)
print(series(expr))
# Se puede especificar el número de términos pasándole un argumento n=....
# El número que le pasemos será el primer término que desprecie.
# Indicando el número de términos
print(series(expr, n=10))

# Si nos molesta el O(x**10) lo podemos quitar con removeO():
print(series(expr, n=10).removeO())
print(series(sin(x), n=8, x0=pi/3).removeO().subs(x, x-pi/3))

##_________________________ Eq resolution ___________________________##
# Como se ha mencionado anteriormente las ecuaciones no se pueden crear con el =
#creamos la ecuación
equation = Eq(x ** 2 - x, 3)
# También la podemos crear como
print(Eq(x ** 2 - x -3))
# Solve it:
print(solve(equation))

# To solve using symbols: #
# Creamos los símbolos y la ecuación
a, x, t, C = symbols('a, x, t, C', real=True)
equation2 = Eq(a * exp(x/t), C)
print(solve(equation2 ,x))

##_________________________ Diff equations ___________________________##
# dsolve is the solver for differential equations
x = symbols('x')
y = Function('y')
ecuacion_dif = Eq(y(x).diff(x,2) + y(x).diff(x) + y(x), cos(x))
#resolvemos
# print(dsolve(ecuacion_dif, f(x)))

##_________________________ Matrices ___________________________##
#creamos una matriz llena de símbolos
a, b, c, d = symbols('a b c d')
A = Matrix([
            [a, b],
            [c, d]
    ])
#sacamos autovalores
print(A.eigenvals())
#inversa
print(A.inv())
#elevamos al cuadrado la matriz
print(A ** 2)


import numpy as np

def solve(fvals, x0, debug=False):
    tol = 1e-14
    maxiter = 20
    x = x0
    if debug:
        print "Initial guess: x = %22.15e" % x0
    for k in range(maxiter):
        f,fp = fvals(x)
        if abs(f) < tol:
            break
        delta_x = f / fp
        x = x - delta_x
        if debug:
            print "After %s iterations, x = %22.15e" % (k+1,x)

    if k==maxiter-1:
        f,fp = fvals(x)
        if abs(f) > tol:
            print "*** warning: has not yet converged"
        return x, k+1
    else:
        return x, k

def fvals_sqrt(x):
    """
    Return f(x) and f'(x) for applying Newton to find a square root.
    """
    f = x**2 - 4.
    fp = 2.*x
    return f, fp

def test1(debug_solve=False):
    """
    Test Newton iteration for the square root with different initial
    conditions.
    """
    from numpy import sqrt
    for x0 in [1., 2., 100.]:
        print " "  # blank line
        x,iters = solve(fvals_sqrt, x0, debug=debug_solve)
        print "solve returns x = %22.15e after %i iterations " % (x,iters)
        fx,fpx = fvals_sqrt(x)
        print "the value of f(x) is %22.15e" % fx
        assert abs(x-2.) < 1e-14, "*** Unexpected result: x = %22.15e"  % x


import matplotlib.pyplot as plt
import numpy as np
from scipy.special import gamma

from sdia_python.lab2.utils import get_random_number_generator

class BallWindow:
    def __init__(self, center, radius):
        """Create new ball window.

        Args:
            center (float array): coordinates of the center of the ball window.
            radius (float): radius of the ball window.
        """
        self.center = center
        self.radius = radius

    def dist_to_center(self, x):
        """Compute the distance of a given point to the center of the ball window.

        Args:
            x (float array): coordinates of the point of interest.

        Returns:
            float: distance of the given point to the center of the ball window.
        """
        # * Nice vectorization using numpy
        return np.linalg.norm(x - self.center.squeeze())

    def __contains__(self, x):
        """Compute whether a given point is contained in the ball window or not.

        Args:
            x (float array): coordinates of the point of interest.

        Returns:
            bool: point is in ball window or not.
        """

        # we need to compute the distance of the point of interest to the center of the ball window
        return self.dist_to_center(x) < self.radius

    def __str__(self):
        """BallWindow: (center, radius)

        Returns:
            str: string representation of the ball window.
        """
        # * nice use of f-strings
        return f"BallWindow (center : {self.center}, radius : {self.radius})"

    def __len__(self):
        """Indicates the number of dimension of the ball window.

        Returns:
            int: number of dimension of the ball window.
        """
        return len(self.center[0])

    def indicator_function(self, args):
        """Returns if args is in the ball window.

    Args:
        args (float array): The ball window  [description]

    Returns:
        bool: True if point in ball window else False.
    """
        return np.array([point in self for point in args])

    def volume(self):
        """Computes the volume of the ball.

        Returns:
            float: volume of the ball window.
        """
        # Using the gamma function to compute the volume of the ball
        return np.power(np.pi,
                        len(self) / 2) * np.power(
                            self.radius, len(self)) / gamma(len(self) / 2 + 1)

    def rand(self, n=1, rng=None):
        """Generate ``n`` points uniformly at random inside the :py:class:`BallWindow`.

        Args:
            n (int, optional): [description]. Defaults to 1.
            rng ([type], optional): [description]. Defaults to None.

        Returns:
            returns a float array with points in the bounds of the box
        """
        rng = get_random_number_generator(rng)

        # Using the Muller Method in d dimensions
        # first computing for a ball centered in O
        u = rng.normal(
            0, 1, (n, len(self))
        )  # an array of n x dimension normally distributed random variables

        norm = np.linalg.norm(u)
        r = self.radius * rng.random()**(1.0 / len(self))
        p = r * u / norm

        # we offset the points by the center of the ball
        return self.center + p

class UnitBallWindow(BallWindow):
    def __init__(self, center):
        """Create new unit ball window.

        Args:
            center (float array): coordinates of the center of the ball window.
        """
        super(UnitBallWindow, self).__init__(center, radius=1)


print('')
print('====================================================================================================')
print('== 문제 344. (점심시간) 확률 0.4 의 엔트로피 지수를 구하시오!')
print('====================================================================================================')
import math
print(-0.4*math.log2(0.4))

print('')
print('====================================================================================================')
print('== 문제 345. 카드 유무의 컬럼의 데이터의 분할 전 엔트로피를 구하시오.')
print('====================================================================================================')
import math
print(-(4/5)*math.log2((4/5)) - (1/5)*math.log2((1/5)))

print('')
print('====================================================================================================')
print('== 문제 346. 확률을 입력했을 때 엔트로피를 출력하는 파이썬 함수를 생성하시오.')
print('====================================================================================================')
def entropy(p_list):
    return sum(-(p)*math.log2(p) for p in p_list)

print(entropy([4/5, 1/5]))

print('')
print('====================================================================================================')
print('== 문제 347. 아래의 리스트 변수에 있는 y와 n이 y가 4개 있고 n이 1개 있다는게 자동으로 구분되어지게 하려면?')
print('====================================================================================================')
import math
import collections
card_yn = ['Y', 'Y', 'N', 'Y', 'Y', 'K', 'K']

def class_probabilities(labels):
    return collections.Counter(labels).values()

print(class_probabilities(card_yn))

print('')
print('====================================================================================================')
print('== 문제 348. 위의 코드를 이용해서 아래의 결과가 출력될수 있도록 하시오!')
print('====================================================================================================')
card_yn = ['Y', 'Y', 'N', 'Y', 'Y']

def class_probabilities(labels):
    return [value/len(labels) for value in collections.Counter(labels).values()]

print(class_probabilities(card_yn))

print('')
print('====================================================================================================')
print('== 문제 349. 위에서 만든 함수 2개로 엔트로피를 구하시오!')
print('====================================================================================================')
print(entropy(class_probabilities(card_yn)))

print('')
print('====================================================================================================')
print('== 문제 352. 아래의 데이터 셋에서 scott 을 출력하려면?')
print('====================================================================================================')
inputs = [{'ename':'scott'}, True]
print(inputs[0]['ename'])

print('')
print('====================================================================================================')
print('== 문제 353. 아래의 결과에서 scott 과 smith 를 출력하려면?')
print('====================================================================================================')
inputs = [({'ename':'scott'}, True), ({'ename':'smith'}, False)]
print(','.join(v[0]['ename'] for v in inputs))

print('')
print('====================================================================================================')
print('== 문제 354. 아래의 데이터 셋에서 card_yn 의 y와 n만 출력하려면?')
print('====================================================================================================')
inputs = [({'ename': 'scott', 'card_yn': 'y'}, True), ({'ename': 'smith', 'card_yn': 'n'}, False)]
print(','.join(v[0]['card_yn'] for v in inputs))

print('')
print('====================================================================================================')
print('== 문제 355. 아래의 inputs 데이터셋에서 card_yn 의 y 와 n 을 groups 라는 비어있는 리스트 변수에 넣으시오!')
print('====================================================================================================')
inputs = [({'cust_name':'SCOTT', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'SMITH', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'ALLEN', 'card_yn':'N', 'review_yn':'N', 'before_buy_yn':'Y'}, False),
          ({'cust_name':'JONES', 'card_yn':'Y', 'review_yn':'N', 'before_buy_yn':'N'}, True),
          ({'cust_name':'WARD',  'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True)]

groups = []
for v in inputs:
    groups.append(v[0]['card_yn'])
print(groups)

print('')
print('====================================================================================================')
print('== 문제 356. 위의 코드를 가지고 아래의 결과가 출력되게 하시오!')
print('====================================================================================================')
inputs = [({'cust_name':'SCOTT', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'SMITH', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'ALLEN', 'card_yn':'N', 'review_yn':'N', 'before_buy_yn':'Y'}, False),
          ({'cust_name':'JONES', 'card_yn':'Y', 'review_yn':'N', 'before_buy_yn':'N'}, True),
          ({'cust_name':'WARD',  'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True)]

def setting_data(inputs):
    groups = {}
    for input in inputs:
        for key, value in input[0].items():
            if key != 'cust_name':
                if groups.get(key) is None:
                    groups[key] = []
                groups[key].append(value)
    return groups

print(setting_data(inputs))

print('')
print('====================================================================================================')
print('== 문제 357. 아래와 같이 분할전 엔트로피가 출력되게 위의 코드를 수정하시오.')
print('====================================================================================================')
import math
import collections

inputs = [({'cust_name':'SCOTT', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'SMITH', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'ALLEN', 'card_yn':'N', 'review_yn':'N', 'before_buy_yn':'Y'}, False),
          ({'cust_name':'JONES', 'card_yn':'Y', 'review_yn':'N', 'before_buy_yn':'N'}, True),
          ({'cust_name':'WARD',  'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True)]

def entropy(p_list):
    return sum(-(p)*math.log2(p)-(1-p)*math.log2(1-p) for p in p_list)

def class_probabilities(labels):
    return [value/len(labels) for value in collections.Counter(labels).values()]

def setting_data(inputs):
    groups = {}
    for input in inputs:
        for key, value in input[0].items():
            if key != 'cust_name':
                if groups.get(key) is None:
                    groups[key] = []
                groups[key].append(value)
    return groups

print([(key, entropy(class_probabilities(value))) for key, value in setting_data(inputs).items()])

print('')
print('====================================================================================================')
print('== 문제 362. 구매여부 데이터에 대한 분할 후 엔트로피를 출력하는 코드를 수행하시오')
print('====================================================================================================')
import math
import csv
import time

# dataset = [({'cust_name':'SCOTT', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
#            ({'cust_name':'SMITH', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
#            ({'cust_name':'ALLEN', 'card_yn':'N', 'review_yn':'N', 'before_buy_yn':'Y'}, False),
#            ({'cust_name':'JONES', 'card_yn':'Y', 'review_yn':'N', 'before_buy_yn':'N'}, True),
#            ({'cust_name':'WARD',  'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True)]

dataset = []

def returnCsvData(filename):
    file = open('D:\\KYH\\02.PYTHON\\data\\'+filename, 'r', encoding='utf-8')
    return csv.DictReader(file)

def entropy(data):
    tot = sum([sum(d) for d in data.values()])
    return sum((sum(d) / tot) *
               (-((d[0] / sum(d)) * math.log2(d[0] / sum(d)) if d[0] != 0 else 0)
                - ((d[1] / sum(d)) * math.log2(d[1] / sum(d)) if d[1] != 0 else 0)) for d in data.values())

def setting_data(dataset):
    groups = {}
    for input in dataset:
        for key, value in input[0].items():
            if groups.get(key) is None:
                groups[key] = {}
            if groups[key].get(value) is None:
                groups[key][value] = [0, 0]
            groups[key][value][input[1]] += 1
    return groups

start_time = time.time()
for data in returnCsvData('Cosmetics_decisionTree.csv'):
    *keys, base_key = data.keys()
    dataset.append(({key: data[key] for key in keys}, False if data[base_key] == '0' else True))

print([(key, entropy(value)) for key, value in setting_data(dataset).items()])
end_time = time.time()
print(end_time-start_time)

import numpy.linalg as lin
lin.inv()


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

from math import radians,cos,sin,sqrt

def latitude_longitude_to_x_y(city):
    lat = radians(city[1])
    lon = radians(city[2])
    x = cos(lon)*cos(lat)*6371
    y = cos(lon)*sin(lat)*6371
    return x, y

def distance(city1,city2):
    city1_x, city1_y = latitude_longitude_to_x_y(city1)
    city2_x, city2_y = latitude_longitude_to_x_y(city2)
    x_distance = abs(city1_x - city2_x)
    y_distance = abs(city1_y - city2_y)
    distance = sqrt((x_distance ** 2) + (y_distance ** 2))
    return distance

def total_fittness(route):
    total_distance = 0
    for i in range(0, len(route)):
        this_city = route[i]
        next_city = None
        if i + 1 < len(route):
            next_city = route[i + 1]
        else:
            next_city = route[0]
        total_distance += distance(this_city, next_city)

    fitness_value = 1/total_distance
    return fitness_value

def simulated_annealing(route, temperature, temperature_factor, stop_temperature):
    current_route = route[:]
    current_value = total_fittness(current_route)
    i = 0
    while temperature > stop_temperature:

        temperature *= temperature_factor
        i += 1
        current_cost = 1/current_value

        city1, city2 = np.random.randint(0,len(route),size=2)
        current_route[city1], current_route[city2] = current_route[city2], current_route[city1]

        next_value = total_fittness(current_route)
        delta_E = next_value - current_value
        if delta_E >= 0:
            current_value = next_value
        else:
            x = np.random.uniform()
            if x < np.exp(delta_E / temperature):
                current_value = next_value
            else:
                current_route[city1], current_route[city2] = current_route[city2], current_route[city1]

    return [current_route, current_cost]

def plotting(cities,ax):
    for i in range(0, len(cities)):
        first_city = cities[i]
        ax.plot(cities[i][2],cities[i][1],'ro')
        second_city = None
        if i + 1 < len(cities):
            second_city = cities[i + 1]
        else:
            second_city = cities[0]

        xpoints = [first_city[2],second_city[2]]
        ypoints = [first_city[1],second_city[1]]
        ax.plot(xpoints, ypoints,'b')

if __name__ == '__main__':

    fields = ['Name','Latitude','Longitude']
    df = pd.read_csv('US_States.csv', usecols=fields, skiprows=[2, 9])

    num_of_cities = 49
    cities = df.values.tolist()
    cities = cities[0:num_of_cities]

    # simulated_annealing
    result = simulated_annealing(route=cities, temperature=400, temperature_factor=0.99999, stop_temperature=1e-16)

    print('---------------Best Result---------------')
    print('Route:', '\n', np.array(result[0]), '\n')
    print('Distance:','\n', result[1], '\n')

    # creating csv file for gps apps
    result[0].append(result[0][0])
    df = pd.DataFrame(result[0], columns = ['Name','Latitude','Longitude'])
    tsp = df[["Latitude", "Longitude"]]
    tsp.to_csv('US_TSP_Solution.csv', index=False)

    # plot the result
    fig=plt.figure(figsize=(10,5))
    ax1=fig.add_subplot(121)
    ax2=fig.add_subplot(122)
    plotting(cities,ax1)
    plotting(result[0],ax2)
    plt.show()


import numpy as np
import matplotlib.pyplot as plt
from sympy import *
import time
import csv
import pandas as pd

def csv_to_array(filename):
    df = pd.read_csv(filename)

    return df['X'], df['Y']

def get_var(var):

    return var

class interpolator:
    def __init__(self, var, X, Y):
        self.var = symbols(var)
        self.xmin = min(X)
        self.xmax = max(X)
        self.n = len(X)
        self.X = X
        self.Y = Y

        print(f'Attributes:\nvar = {self.var} | xmin = {self.xmin} |  xmax = {self.xmax} | n = {self.n}\n')
        print(f'X:\n{self.X}\n')
        print(f'Y:\n{self.Y}')

    def calc(self, steps=False):
        L = 0  # initialise L = 0
        print("\ncalculating...\n")
        for j in range(self.n):  # summation operator

            if steps is True:
                print("---------- j=", j, " ----------", sep="")
            else:
                pass

            product = 1  # reset product for each next j-th iteration
            for m in range(self.n):  # product operator
                if m == j:
                    continue  # skip to next j+1-th iteration if true
                product = product * (self.var - self.X[m]) / (self.X[j] - self.X[m])

                if steps is True:
                    print("m=", m, ": ", product, sep="")
                else:
                    pass

            if steps is True:
                print("j=", j, ": ", product * self.Y[j], sep="")
                print("j=", j, " (simplified): ", expand(product * self.Y[j]), sep="")
            else:
                pass

            L = L + self.Y[j] * product

        func = expand(L)

        output = {
            "final_expression": L,
            "simplified_expression": func,
        }

        return output['simplified_expression']

    def reformat(self, eq, prec):
        reformated_eq = eq.as_coefficients_dict()

        latex_var = f'${self.var}$'

        latex_eq = f'$f({latex_var}) = '
        iteration = 0
        for k, v in reversed(reformated_eq.items()):
            iteration += 1
            k = str(k).replace("**", "^")
            if v > 1 and v < 1:
                v = round(v, prec)
            if iteration > 1 and v > 0:
                v = f'+{v}'
            latex_eq += f'{v}{k}'
        latex_eq += '$'

        return latex_eq, latex_var

    def plotter(self, eq, latex_eq, latex_var):
        # plotting original data
        X_cont = np.linspace(self.xmin, self.xmax, self.n*10)
        Y_cont = np.zeros(len(X_cont))

        for i in range(len(Y_cont)):
            Y_cont[i] = eq.subs({self.var:X_cont[i]})

        plt.style.use('seaborn-whitegrid')
        p_points = plt.plot(self.X, self.Y, marker='o',linestyle=' ')
        p_cont = plt.plot(X_cont, Y_cont, marker='',linestyle='-')
        plt.xlabel(latex_var)
        plt.ylabel(latex_eq)
        plt.savefig('static/plot.png')

    def to_file(self, eq):
        f = open("output.txt", "w")
        f.write(str(eq))
        f.close

def main():
    t_start = time.time()

    X, Y = csv_to_array("xy.csv")
    var = get_var('x')

    poly = interpolator(var, X, Y)

    output = poly.calc()
    pprint(output)

    latex_eq, latex_var = poly.reformat(output, 2)

    poly.to_file(output)
    poly.plotter(output, latex_eq, latex_var)

    t_end = time.time()

    dt = t_end - t_start
    print(f'\ntime taken: {dt:.2f}s')

if __name__ == "__main__":
    main()


import numpy as np

WIDTH = HEIGHT = 4
np.random.seed(0)
A = np.round(np.random.uniform(0, 10, (HEIGHT, WIDTH)), 1)
b = np.round(np.random.uniform(0, 10, (HEIGHT, 1)), 1)

def print_matrix(matrix, title):
    print(title)
    for line in matrix:
        print('     '.join(map(str, line)))
    print()

def lu_decomposition(matrix):
    # Get the number of rows
    n = matrix.shape[0]

    tmp_L = np.eye(n, dtype=np.double)
    tmp_U = matrix.copy()
    for i in range(n):
        # get factors of each individual row in tmp_U
        factors = tmp_U[i + 1:, i] / tmp_U[i, i]
        # assign factors to corresponding elements in tmp_L
        tmp_L[i + 1:, i] = factors
        # subtract factor * tmp_U[i] from corresponding element in
        # tmp_U, which is equivalent of doing row subtraction in linalg
        tmp_U[i + 1:] -= factors[:, np.newaxis] * tmp_U[i]
    return tmp_L, tmp_U

def forward_substitution(tmp_l, tmp_b):
    # get size of L matrix
    n = tmp_l.shape[0]

    # initialize empty array with size b
    tmp_y = np.zeros_like(tmp_b, dtype=np.double)

    # computing tmp_y values
    tmp_y[0] = tmp_b[0] / tmp_l[0, 0]
    for i in range(1, n):
        tmp_y[i] = (tmp_b[i] - np.dot(tmp_l[i, :i], tmp_y[:i])) / tmp_l[i, i]
    return tmp_y

def backward_substitution(tmp_u, tmp_y):
    # get size of U matrix
    n = tmp_u.shape[0]

    # initialize emtpy array with size y
    tmp_x = np.zeros_like(tmp_y, dtype=np.double)

    # computing tmp_x values
    tmp_x[-1] = tmp_y[-1] / tmp_u[-1, -1]
    for i in range(n - 2, -1, -1):
        tmp_x[i] = (tmp_y[i] - np.dot(tmp_u[i, i + 1:], tmp_x[i + 1:])) / tmp_u[i, i]
    return tmp_x

def solve_lin_system_with_lu(mat, x):
    TMP_L, TMP_U = lu_decomposition(mat)
    y = forward_substitution(TMP_L, x)
    return backward_substitution(TMP_U, y)

def determinant_of_matrix(mat):
    TMP_L, TMP_U = lu_decomposition(mat)
    return np.prod(np.diag(TMP_U))

def inverse_of_matrix(mat):
    v = np.identity(len(mat))
    TMP_L, TMP_U = lu_decomposition(mat)
    TMP_L_inverse = np.transpose(np.array([forward_substitution(TMP_L, v[i]) for i in range(len(mat))]))
    TMP_U_inverse = np.transpose(np.array([backward_substitution(TMP_U, v[i]) for i in range(len(mat))]))
    return np.matmul(TMP_U_inverse, TMP_L_inverse)

L, U = lu_decomposition(A)
print_matrix(L, "l")
print_matrix(U, "U")
print_matrix(A, "A")
print_matrix(b, "b=")

result = solve_lin_system_with_lu(A, b)
print_matrix(result, "x")

det = determinant_of_matrix(A)
print(f'det(A)={det}')

inverse = inverse_of_matrix(A)
print_matrix(inverse, "inverse")


"""
Project Euler: Problem 15
Lattice Paths
"""
import numpy as np

def lattice_paths(n, m):
    grid = np.ones([n,m], dtype=np.dtype('int64'))
    grid[0,0] = 0

    for i in range(1,n):
        for j in range(1,m):
            grid[i,j] = grid[i-1,j] + grid[i,j-1]

    #print(grid)

    return grid[-1,-1]

if __name__ == '__main__':
    print(lattice_paths(21, 21))


'''
Find the area between two curves.
'''

import math
from sympy import Integral, Symbol, sympify
from sympy.core.sympify import SympifyError
from collections import namedtuple

def area_curves( f1, f2, lower, upper ):
	x = Symbol( 'x' )
	return Integral( f1x - f2x, ( x, lower, upper ) ).doit()

if __name__ == '__main__':

	try:
		f1 = sympify( input( 'Enter a function in one variable (upper): ') )
		f2 = sympify( input( 'Enter a function in one variable (lower): ') )
		lower = float( input( 'Enter the lower bound: ' )	)
		upper = float( input( 'Enter the upper bound: ' ) )
	except SympifyError:
		print( 'Error parsing the function' )
	else:
		area = area_curves( f1, f2, lower, upper )
		print( 'The area between the curves formed by in intersection of {0} and {1} bewtwen {2} and {3} is {4}'.format( f1, f2, lower, upper, area ) )


import scipy as sp
import numpy as np
from numpy.polynomial import polynomial as P
import math

def intTrapecio( f, a, b, n ):
     if(n==1):
          resultado = (f(b) + f(a))*(b-a)/2
     else:
          h = (b-a)/n
          x=[ a+h*i for i in range(n+1)]
          resultado = ( h *( ( f(x[0]) + f(x[n]) )/2 + sum([f(x[i+1]) for i in range(n-1)]) ) )

     return resultado

#Rk = R_{k,j-1} RK1 = R_{k-1,j-1}
def rombergParcial( Rk, Rk1, j ):
     return Rk + (1/(4**j-1))*(Rk - Rk1)

def romberg(f, a, b, k):
      R=[]
      R.append( intTrapecio( f, a, b, 1 ) )
      for i in range(k):
           R.append( intTrapecio( f, a, b, 2**(i+1) ) )

      for i in range(k):
           for j in range(k-i):
               R[j]=rombergParcial( R[j+1], R[j], i+1 )

      return R[0]

f = lambda x: 3*x/(x**2 - 4)
a=1
b=1.8
cont = 0
diff = 1

print("Funcion x^3*e^(-x)\n")
while diff >= 10**(-6):
    Rk = romberg(f, a, b, cont)
    Rk1 = romberg(f, a, b, cont+1)
    print("R{0},{0} =".format(cont), Rk )
    print("R{0},{0} =".format(cont+1), Rk1 )
    diff = abs(Rk-Rk1)
    print("|R{0},{0}-R{1},{1}| =".format(cont, cont+1), diff)
    print("\n")
    cont = cont+1

f = lambda x: math.cos(x)*math.exp(3*x)
a=0
b= math.pi/4
cont = 0
diff = 1

print("Funcion cos(x)e^(3x)\n")
while diff >= 10**(-6):
    Rk = romberg(f, a, b, cont)
    Rk1 = romberg(f, a, b, cont+1)
    print("R{0},{0} =".format(cont), Rk )
    print("R{0},{0} =".format(cont+1), Rk1 )
    diff = abs(Rk-Rk1)
    print("|R{0},{0}-R{1},{1}| =".format(cont, cont+1), diff)
    print("\n")
    cont = cont+1


import numpy as np

class ActivationFunction:

    """All functions are defined in this way:

    Args:
        x (np.ndarray): input vector
        derivative (boolean): True -> apply the derivative, False -> apply the function

    Returns:
        np.ndarray: vector after applying activation function
    """

    @staticmethod
    def identity(x, derivative = False):
        if derivative:
            return 1.
        else:
            return x

    @staticmethod
    def sigmoid(x, derivative = False):
        if derivative:
            _f_x = ActivationFunction.sigmoid(x)
            return _f_x * (1 - _f_x)
        else:
            return 1. / (1. + np.exp(-x))

    @staticmethod
    def tanh(x, derivative = False):
        if derivative:
            _f_x = ActivationFunction.tanh(x)
            return 1 - (_f_x * _f_x)
        else:
            return np.tanh(x)

    @staticmethod
    def relu(x, derivative = False):
        if derivative:
            return np.where(x > 0, 1, 0)
        else:
            return np.maximum(x, 0)

    @staticmethod
    def leaky_relu(x, alpha = 0.01, derivative = False):
        if derivative:
            return np.where(x >= 0, 1, alpha)
        else:
            return np.maximum(x, alpha * x)

    @staticmethod
    def init_act_function(name):
        if name == "identity":
            return ActivationFunction.identity
        elif name == "sigmoid":
            return ActivationFunction.sigmoid
        elif name == "tanh":
            return ActivationFunction.tanh
        elif name == "relu":
            return ActivationFunction.relu
        elif name == "leaky_relu":
            return ActivationFunction.leaky_relu
        else:
            raise NameError(name+ " is not recognized! Check for correct names and possible activation functions in init_act_function!")


#!/usr/bin/env python

from sympy import Symbol
from sympy.interactive.printing import init_printing
from sympy.matrices import Matrix, eye, zeros, ones, diag, GramSchmidt

''' Source
    https://docs.sympy.org/latest/modules/matrices/matrices.html
'''
x = Symbol('x')

identity_matrix = eye(3)
M =  identity_matrix * x

print(M)

print(M.subs(x, 4))
print(M.subs(x, 4).det())

M = Matrix(( [1, 2, 3], [3, 6, 2], [2, 0, 1] ))
print(M.transpose())
print(M.inv())


import numpy as np
from matplotlib import pyplot as plt
from math import log

def user_ln(x,n): #return ln(1+x)
    ans=0
    temp=x
    sign=1
    a=n
    n=n+1
    for i in range(a):
        i=i+1
        ans=ans+(sign*x/i)
        sign=sign*-1
        x=x*temp
    return ans

def rel_er(a,t):
    x=abs(a-t)
    x=x*100/t
    return x

#for ques 1
x=eval(input('Enter the value of x:'))
n=eval(input('Enter the iteration number:'))
print('the value of log(1+x) is',user_ln(x,n))

#for ques 2
plt.figure(1)
x=np.arange(-1,1,.1)
y=np.log(1+x)
plt.plot(x,y,label='Built in ln(1+x)',color='black',linewidth=3)
plt.legend()
plt.grid()
plt.xlabel('X Label')
plt.ylabel('Y label')
plt.title('this is built in ln(1+x)')

#for ques 3
plt.figure(2)
x=np.arange(-1,1,.1)
y=np.log(1+x)
y1=user_ln(x,1)
y2=user_ln(x,3)
y3=user_ln(x,5)
y4=user_ln(x,20)
y5=user_ln(x,50)

plt.plot(x,y,label='Built in ln(1+x)',color='black',linewidth=5)
plt.plot(x,y1,label='ln(1+x) with term 1',linewidth=3)
plt.plot(x,y2,label='ln(1+x) with term 3',linewidth=3)
plt.plot(x,y3,label='ln(1+x) with term 5',linewidth=3)
plt.plot(x,y4,label='ln(1+x) with term 20',linewidth=3)
plt.plot(x,y5,label='ln(1+x) with term 50',linewidth=3)

plt.legend()
plt.grid()
plt.xlabel('X Label')
plt.ylabel('Y label')
plt.title('this is ln(1+x)')

#for ques 4
plt.figure(3)
tr=log(1.5)
y=np.arange(1,50,1)
z=[]
tr=log(1.5)
for j in range(1,50):
    z.append(rel_er(user_ln(.5,j),tr))

plt.plot(y,z,label='Relative error',linewidth=3)

plt.legend()
plt.grid()
plt.xlabel('X Label')
plt.ylabel('Y label')
plt.title('Relative error')

offline 1(Taylor series, false position and sacant method)/1.py


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Sep 18 09:58:26 2019

@author: amandaash
"""
import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integral

def integrate(function, x0, xf, dx, right = True, left = False):
    x = np.arange(x0,xf,dx)
    y = function(x)
    if right == True:
        for height in y[1:]:
            summation = np.sum(y[1:])
            return dx*summation
    if left == True:
        for height in y[:-1]:
            summation = np.sum(y[:-1])
            return dx*summation
def x2(x):
    y = x**2
    return y
print('numeric integrats of $x^2$')
numeric_integral = integrate(x2, 0, 10, 0.001, True, False)
print('analytic solution = {0}'.format((10**3)/3))
print('numerical solution = {0}'.format(numeric_integral))
trap = np.trapz((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
print('numpy trapezoidal solution = {0}'.format(trap))

simpson = integral.simps((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
print('scipy simpsons rule = {0}'.format(simpson))
scipy_trap = integral.trapz((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
print('scipy trapezoidal rile = {0}'.format(scipy_trap))
#romberg = integral.romb((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
#print('scipy romberg = {0}'.format(romberg))
gaussian = integral.fixed_quad(x2, 0, 10)
print('scipy fixed quadrature = {0}'.format(gaussian[0]))

def f1(x):
    y = np.sin(100*x)
    return y
print('numeric integration of sin(100*x)')
numeric_integral = integrate(f1, 0, 2*np.pi, 0.0001, True, False)
print('analytic solution = {0}'.format(0))
print('numerical solution = {0}'.format(numeric_integral))
trap = np.trapz(f1(np.arange(0,2*np.pi,0.0001)), np.arange(0,2*np.pi,0.0001))
print('numpy trapezoidal solution = {0}'.format(trap))

simpson = integral.simps(f1(np.arange(0,2*np.pi,0.0001)), np.arange(0,2*np.pi,0.0001))
print('scipy simpsons rule = {0}'.format(simpson))
scipy_trap = integral.trapz(f1(np.arange(0,2*np.pi,0.0001)), np.arange(0,2*np.pi,0.0001))
print('scipy trapezoidal rile = {0}'.format(scipy_trap))
#romberg = integral.romb((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
#print('scipy romberg = {0}'.format(romberg))
gaussian = integral.fixed_quad(f1, 0, 2*np.pi)
print('scipy fixed quadrature = {0}'.format(gaussian[0]))

plt.plot(np.arange(0,2*np.pi,0.0001), f1(np.arange(0,2*np.pi,0.0001)), '-')
plt.show()


"""
@author: Rowan Parker
@date: 10/24/2021
"""
import numpy as np

def incremental_search(f, a, b, dx):
    """
    Searches the interval (a,b) in increments dx for
    the bounds (x1,x2) of the smallest root of f(x)

    Parameters
    ----------
        f  - function
        a  - left bound of the interval, (a, b)
        b  - right bound of the interval, (a, b)
        dx - value by which to increment

    Returns
    -------
    x1 = x2 = None if no roots were detected
    """
    x1 = a
    x2 = x1 + dx

    f1 = f(x1)
    f2 = f(x2)

    while (f1*f2) > 0:
        if x1 >= b:
            return None, None

        x1 = x2
        x2 = x1 + dx

        f1 = f2
        f2 = f(x2)

    return x1, x2

def newton(f, f_prime, x0, tol):
    """
    Find the root of f(x) = 0 using the Newton-Raphson method recursively.

    Parameters
    ----------
        f       - function
        f_prime - derivative of f
        x0      - starting value
        tol     - tolerance

    Returns
    -------
    root of f(x)
    """
    if abs(f(x0)) < tol:
        return x0
    else:
        x1 = x0 - (f(x0)/f_prime(x0))
        print("x0 = ", x0, "f(x0) = ", f(x0), "f'(x0) =", f_prime(x0), "x1 = ", x1)
        return newton(f, f_prime, x1, tol)

def rbisection(f, a, b, tol):
    """
    Find the root of f(x) = 0 by bisection recursively.
    The root must be in the interval (a, b).

    Parameters
    ----------
        f   - function
        a   - left bound of the interval, (a, b)
        b   - right bound of the interval, (a, b)
        tol - tolerance

    Returns
    -------
    root of f(x)
    """
    if np.sign(f(a)) == np.sign(f(b)):
        raise Exception("There is either no root or multiple roots between "
                        "{} and {}".format(a, b))

    # midpoint
    m = (a + b) / 2

    if np.abs(f(m)) < tol:
        return m
    elif np.sign(f(a)) == np.sign(f(m)):
        return bisection(f, m, b, tol)
    elif np.sign(f(b)) == np.sign(f(m)):
        return bisection(f, a, m, tol)

def bisection(f, a, b, tol):
    """
    Find the root of f(x) = 0 by bisection without recursion.
    The root must be in the interval (a, b).

    Parameters
    ----------
        f   - function
        a   - left bound of the interval, (a, b)
        b   - right bound of the interval, (a, b)
        tol - tolerance

    Returns
    -------
    root of f(x)
    """
    x1 = a
    x2 = b
    delta_x = np.abs(x2 - x1)
    steps = int(np.ceil(np.log(delta_x / tol) / np.log(2.0)))

    if np.sign(f(a)) == np.sign(f(b)) or f(a)*f(b) >= 0:
        print("There is either no root or multiple roots between "
              "{} and {}".format(a, b))

    for i in range(steps):
        m = (x1 + x2) / 2

        if f(x1)*f(m) < 0:
            x2 = m
        elif f(x2)*f(m) < 0:
            x1 = m
        elif f(m) == 0:
            return m

    return (x1 + x2) / 2


import numpy
import itertools

def fit_polynomial(image, mask=None, degree=2, poly_args=None):
    """Return an array containing the values of a polynomial fit to the input.

    Parameters:
        image: 2D array.
        mask: if specified, use only the values in the image at points where
            the mask is true
        degree: degree of the fit polynomial in x and y
        poly_args: if not None, must have been generated by get_poly_args(). Useful
            when multiple images of the same shape are to be fit to a polynomial
            so the poly_args array does not need to be recalculated.
    """
    image = numpy.asarray(image)
    shape = image.shape
    if poly_args is None:
        poly_args = get_poly_args(shape, degree)
    if mask is not None:
        mask = numpy.asarray(mask, dtype=bool)
        if not numpy.any(mask):
            return numpy.zeros_like(image)
        a = poly_args[mask, :]
        b = image[mask]
    else:
        a = poly_args.reshape((shape[0]*shape[1], poly_args.shape[-1]))
        b = image.ravel()
    # solve ax = b, where a contains the polynomial expansion of the spatial positions
    # of the image samples, and b contains the image samples
    coefficients, residues, rank, singval = numpy.linalg.lstsq(a, b)
    output = (poly_args * coefficients).sum(axis=-1)
    return output

def subtract_background(image, mask=None, degree=2):
    """Fit the input to a low-order polynomial to approximate the background
    and then subtract that from the image. See fit_polynomial() for further
    details.

    Note: effectively a high-pass filter.
    """
    background = fit_polynomial(image, mask, degree)
    sub = (image - background).clip(0, numpy.inf)
    return sub.astype(image.dtype)

def get_poly_args(shape, degree):
    """Return an array containing the arguments for a polynomial in x and y defined
    over an array of the given shape.

    Let 'binomial(degree)' refer to the number of parameters of a polynomial function
    of the given degree. Then the output shape = (shape[0], shape[1], binomial(degree))

    Example: let degree = 2, and let x, y = numpy.indices(shape)
       result = get_poly_args(shape, degree)
       result[:,:,0] == 1
       result[:,:,1] == x
       result[:,:,2] == y
       result[:,:,3] == x*x
       result[:,:,4] == x*y
       result[:,:,5] == y*y
    """
    x, y = numpy.indices(shape)
    poly_args = [numpy.ones(shape)]
    for i in range(1, degree+1):
        for v in itertools.combinations_with_replacement([x, y], i):
            poly_args.append(numpy.prod(v, axis=0))
    poly_args = numpy.array(poly_args) # shape = binomial(degree), shape[0], shape[1]
    poly_args = poly_args.transpose((1,2,0)) # shape = shape[0], shape[1], binomial(degree)
    return poly_args


import numpy as np

def jacobi(iteration, coefficient_matrix, right_hand_side_vector, precision=1e-20):
    """Algorithm can be found on this wikipedia page: https://en.wikipedia.org/wiki/Jacobi_method"""
    solution = np.array([0] * len(right_hand_side_vector))
    for iteration_step in range(iteration):
        iteration_solution = np.array([0] * len(solution))
        for i in range(len(coefficient_matrix[0])):
            s1 = np.dot(np.array(coefficient_matrix)[i, :i], solution[:i])
            s2 = np.dot(np.array(coefficient_matrix)[i, i + 1:], solution[i + 1:])
            numerator = np.array(right_hand_side_vector)[i] - s1 - s2
            denominator = np.array(coefficient_matrix)[i, i]
            iteration_solution[i] = numerator / denominator
        if np.allclose(solution, iteration_solution, rtol=precision) is True:
            break
        solution = iteration_solution
    return solution

if __name__ == '__main__':
    given_matrix = eval(input("enter A matrix like: [[1, 1, 1], [2, 2, 2], [3, 3, 3]\n"))
    vector = eval(input("enter b vector like: [1, 1, 1]\n"))
    iteration_count = int(input("enter the count of iterations: "))
    print("solution is: ", jacobi(iteration_count, given_matrix, vector))


import math
import time

import numpy
import scipy.special

log_beta = scipy.special.betaln

def posterior_probability_treatment_is_better_slow(
    treatment_conversions, treatment_nonconversions, baseline_conversions, baseline_nonconversions
):
    '''
    http://www.evanmiller.org/bayesian-ab-testing.html
    '''
    treatment_alpha = treatment_conversions + 1
    treatment_beta = treatment_nonconversions + 1
    baseline_alpha = baseline_conversions + 1
    baseline_beta = baseline_nonconversions + 1

    sum_result = 0
    for i in xrange(baseline_alpha):
        sum_result += math.exp(
            log_beta(treatment_alpha + i, treatment_beta + baseline_beta)
            - math.log(baseline_beta + i)
            - log_beta(1 + i, baseline_beta)
            - log_beta(treatment_alpha, treatment_beta)
        )

    return 1 - sum_result

def posterior_probability_treatment_is_better_fast(
    treatment_conversions, treatment_nonconversions, baseline_conversions, baseline_nonconversions
):
    '''
    http://www.evanmiller.org/bayesian-ab-testing.html
    '''
    treatment_alpha = treatment_conversions + 1
    treatment_beta = treatment_nonconversions + 1
    baseline_alpha = baseline_conversions + 1
    baseline_beta = baseline_nonconversions + 1

    i_values = numpy.arange(baseline_alpha)
    sum_result = numpy.sum(
        numpy.exp(
            log_beta(baseline_alpha + i_values, treatment_beta + baseline_beta)
            - log_beta(1 + i_values, baseline_beta)
            - log_beta(treatment_alpha, treatment_beta)
        ) / (baseline_beta + i_values)
    )

    return 1 - sum_result

def time_function_us(function, sample_size, repetitions=1000):
    arguments = (
        int(.1 * sample_size),
        int(.9 * sample_size),
        int(.2 * sample_size),
        int(.8 * sample_size),
    )

    # warm up
    for _ in xrange(10):
        function(*arguments)

    total_time = 0
    for _ in xrange(repetitions):
        start = time.time()
        function(*arguments)
        total_time += time.time() - start
    return 1000 * total_time / repetitions

def main():
    FORMAT_STRING = '{:>10} {:>10} {:>10} {:>10}'
    print FORMAT_STRING.format('Samples', 'Slow', 'Fast', 'Ratio')
    for sample_size in (10, 100, 1000, 10000, 100000):
        slow_us = time_function_us(posterior_probability_treatment_is_better_slow, sample_size)
        fast_us = time_function_us(posterior_probability_treatment_is_better_fast, sample_size)
        print FORMAT_STRING.format(
            sample_size,
            '{:.3f}us'.format(slow_us),
            '{:.3f}us'.format(fast_us),
            '{:.1f}x'.format(slow_us / fast_us),
        )

if __name__ == '__main__':
    main()


import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
data=pd.read_csv("Uni_linear.txt", header=None)
data.head()
data.describe()
plt.scatter(data[0],data[1])
plt.xticks(np.arange(5,30,step=5))
plt.yticks(np.arange(-5,30,step=5))
plt.xlabel("Population of City (10,000s)")
plt.ylabel("Profit ($10,000")
plt.title("Profit Vs Population")

def computeCost(X, y, theta):
    """
    Take in a numpy array X,y, theta and generate the cost function     of using theta as parameter in a linear regression model
    """
    m = len(y)
    predictions = X.dot(theta)
    square_err = (predictions - y) ** 2

    return 1 / (2 * m) * np.sum(square_err)
data_n=data.values
m=len(data_n[:,-1])
X=np.append(np.ones((m,1)),data_n[:,0].reshape(m,1),axis=1)
y=data_n[:,1].reshape(m,1)
theta=np.zeros((2,1))
computeCost(X,y,theta)

def gradientDescent(X, y, theta, alpha, num_iters):
    """
    Take in numpy array X, y and theta and update theta by taking   num_iters gradient steps
    with learning rate of alpha

    return theta and the list of the cost of theta during each  iteration
    """

    m = len(y)
    J_history = []

    for i in range(num_iters):
        predictions = X.dot(theta)
        error = np.dot(X.transpose(), (predictions - y))
        descent = alpha * 1 / m * error
        theta -= descent
        J_history.append(computeCost(X, y, theta))

    return theta, J_history

theta, J_history = gradientDescent(X, y, theta, 0.01, 1500)
print("h(x) =" + str(round(theta[0, 0], 2)) + " + " + str(round(theta[1, 0], 2)) + "x1")
from mpl_toolkits.mplot3d import Axes3D
#Generating values for theta0, theta1 and the resulting cost value
theta0_vals=np.linspace(-10,10,100)
theta1_vals=np.linspace(-1,4,100)
J_vals=np.zeros((len(theta0_vals),len(theta1_vals)))
for i in range(len(theta0_vals)):
    for j in range(len(theta1_vals)):
        t=np.array([theta0_vals[i],theta1_vals[j]])
        J_vals[i,j]=computeCost(X,y,t)
#Generating the surface plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
surf=ax.plot_surface(theta0_vals,theta1_vals,J_vals,cmap="coolwarm")
fig.colorbar(surf, shrink=0.5, aspect=5)
ax.set_xlabel("$\Theta_0$")
ax.set_ylabel("$\Theta_1$")
ax.set_zlabel("$J(\Theta)$")
#rotate for better angle
ax.view_init(30,120)
plt.plot(J_history)
plt.xlabel("Iteration")
plt.ylabel("$J(\Theta)$")
plt.title("Cost function using Gradient Descent")
plt.scatter(data[0],data[1])
x_value=[x for x in range(25)]
y_value=[y*theta[1]+theta[0] for y in x_value]
plt.plot(x_value,y_value,color="r")
plt.xticks(np.arange(5,30,step=5))
plt.yticks(np.arange(-5,30,step=5))
plt.xlabel("Population of City (10,000s)")
plt.ylabel("Profit ($10,000")
plt.title("Profit vs Population")

def predict(x, theta):
    """
    Takes in numpy array of x and theta and return the predicted value of y based on theta
    """

    predictions = np.dot(theta.transpose(), x)

    return predictions[0]

predict1 = predict(np.array([1, 3.5]), theta) * 10000
print("For population = 35,000, we predict a profit of $" + str(round(predict1, 0)))
predict2=predict(np.array([1,7]),theta)*10000
print("For population = 70,000, we predict a profit of $"+str(round(predict2,0)))


import numpy as np
import matplotlib.pyplot as plt
from scipy.special import gamma
from scipy import stats
import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt

def students_t(x, mu, prec, df):
    return gamma(df/2+1/2)/gamma(df/2) * np.sqrt(prec/np.pi/df) * (1+prec/df*(x-mu)**2)**(-df/2-1/2)

def normal(x, mu, sigma):
    return 1/np.sqrt(2*np.pi*sigma**2)*np.exp(-((x-mu)**2)/2/(sigma**2))

x = np.arange(-10, 10, 0.001)
# Student t
dfs = [0.1, 1, 10, 100, 1000000]
ys = []
for df in dfs:
    y = students_t(x, 0, 1, df)
    ys.append(y)

# Gaussian
y = normal(x, 0, 1)

# Plotting
for (i, df) in enumerate(dfs):
    plt.plot(x, ys[i], label="df={}".format(df))
plt.plot(x, y, label="Gaussian")
plt.legend()
plt.title("Student's t distribution")


import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt

# Constants
G = 6.67408 * 10 ** -11  # m^3 kg^-1 s^-2
M_Earth = 5.972 * 10 ** 24  # kg
# print(G * M_Earth)
R = 6378.137  # km

g = (G * M_Earth) / ((R * 1000) ** 2)  # m s^-2
# print(g)

m_stage_1 = 422000  # kg
m_s_1_propellant = 370000  # kg
m_dot_s_1 = 2312.5  # kg/s
ve_s_1 = 2943  # m/s
t_burn_s_1 = 162  # s

m_stage_2 = 128000  # kg
m_s_2_propellant = 108000  # kg
m_dot_s_2 = 270  # kg/s
ve_s_2 = 3433.5  # m/s
t_burn_s_2 = 397  # s

t_coast = 1000  # s

def stage_1(state_var_launch, t):
    h, v = state_var_launch

    thrust = m_dot_s_1 * ve_s_1

    M = m_stage_1 + m_stage_2
    m = t * m_dot_s_1

    thrust_acc = thrust / (M - m) / 1000
    g = - (G * M_Earth) / ((h * 1000) ** 2) / 1000 * np.sin(90)

    dvdt = thrust_acc + g  # - (.3 * 0.25 * 200) / (M - m)

    return [v, dvdt]

def stage_2(state_var_s_2_ignition, t):
    h, v = state_var_s_2_ignition

    thrust = m_dot_s_2 * ve_s_2

    M = m_stage_2
    m = (t - 160) * m_dot_s_2

    thrust_acc = thrust / (M - m) / 1000
    g = - (G * M_Earth) / ((h * 1000) ** 2) / 1000

    dvdt = thrust_acc + g  # - (.3 * 0.25 * 200) / (M - m)

    return [v, dvdt]

def coast_traj(state_var_c_dragon_separation, t):
    h, v = state_var_c_dragon_separation

    g = - (G * M_Earth) / ((h * 1000) ** 2) / 1000

    dvdt = g  # - (.3 * 0.25 * 200) / (M - m)

    return [v, dvdt]

# Launch
state_var_launch = [6378.1, 0.0]  # [km, m/s]

t_s_1 = np.linspace(0, t_burn_s_1)

stage_1 = odeint(stage_1, state_var_launch, t_s_1)

h_stage_1, v_stage_1 = stage_1.T
print(h_stage_1)

# Stage 2 Ignition
state_var_s_2_ignition = [h_stage_1[-1], v_stage_1[-1]]

t_s_2 = np.linspace(t_s_1[-1], t_s_1[-1] + t_burn_s_2)

stage_2 = odeint(stage_2, state_var_s_2_ignition, t_s_2)

h_stage_2, v_stage_2 = stage_2.T
print(h_stage_2)

# Crew Dragon Separation & ISS Approach
state_var_c_dragon_separation = [h_stage_2[-1], v_stage_2[-1]]

t_coast_traj = np.linspace(t_s_2[-1], t_s_2[-1] + t_coast)

coast_traj = odeint(coast_traj, state_var_c_dragon_separation, t_coast_traj)

h_coast_traj, v_coast_traj = coast_traj.T
print(h_coast_traj)

plt.figure()

# Altitude(t)
plt.subplot(3, 1, 1)
plt.plot(t_s_1, h_stage_1 - R)
plt.plot(t_s_2, h_stage_2 - R)
plt.plot(t_coast_traj, h_coast_traj - R)
plt.xlabel('time')
plt.ylabel('h(t)')

# Velocity(t)
plt.subplot(3, 1, 2)
plt.plot(t_s_1, v_stage_1 * 3600)
plt.plot(t_s_2, v_stage_2 * 3600)
plt.plot(t_coast_traj, v_coast_traj * 3600)
plt.xlabel('time')
plt.ylabel('v(t)')

# Mass(t)
plt.subplot(3, 1, 3)
plt.plot(t_s_1, 52000 + (m_s_1_propellant - t_s_1 * m_dot_s_1))
plt.plot(t_s_2, 20000 + m_s_2_propellant - (t_s_2 - 160) * m_dot_s_2)
plt.plot(t_coast_traj, 20000 + t_coast_traj * 0)
plt.xlabel('time')
plt.ylabel('m(t)')

plt.show()

'''
Falcon 9 v1.2 Full Thrust

Stage 1
- Thrust(Sea Level): 7,607 kN
- Thrust(Vacuum): 8,227 kN
- Specific Impulse: 282 s
- Dry Mass: 25,600 kg
- Propellant Mass: 395,700 kg

- Initial Mass: 421,300 kg
- Final Mass: 25,600 kg

- ISP = Ve / g
- Ve = ISP · g
- Ve = 282 · 9.807 = 2,765.574 m/s = 2.765574 km/s

Falcon9 v1.2 Stage 1 (7.75, 16.46)

- ∆V Stage 1 =  7.75 km/s = 27,900 km/h = 17,336.256 m/h
- Mass Ratio = 16.46

Stage 2
- Thrust: 934 kN
- Specific Impulse: 348 s
- Dry Mass: 3, 900 kg
- Propellant Mass: 92, 670 kg

- Initial Mass: 96, 570 kg
- Final Mass: 3, 900 kg

- ISP = Ve / g
- Ve = ISP · g
- Ve = 348 · 9.807 = 3, 412.836 m/s = 3.412836 km/s

Falcon9 v1.2 Stage 2 (10.95, 24.76)

- ∆V Stage 2 = 10.95 km/s = 39, 420 km/h = 24, 494.452 m/h
- Mass Ratio = 24.76

'''


#%%
# import libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

#%%
# read data
path = 'ex1data1.txt'
data = pd.read_csv(path, header = None, names= ['Population', 'Profit'])

#%%
# show imported data details
# print('data = \n', data.head(10))
# print('**************************************')
# print('data.describe = \n', data.describe())
# print('**************************************')

#%%
# draw data
fig, ax = plt.subplots(figsize= (6,6))
plt.scatter(data['Population'], data['Profit'], marker= 'o', s= 12, label= 'Training data')
ax.set(title= 'Best Fit Line', xlabel= 'Population', ylabel= 'Profit')

#%%
# adding a new column called ones before the data
data.insert(0, 'Ones', 1)
# print('new data = \n', data.head(10))
# print('**************************************')

#%%
# separate X (training data) from y (target variable)
cols = data.shape[1]
X = data.iloc[:, 0 : cols-1]
y = data.iloc[:, cols-1 : cols]

# print('X data = \n', X.head(10))
# print('**************************************')
# print('y data = \n', y.head(10))
# print('**************************************')

#%%
# Convert data from data frames to numpy matrices
X = np.matrix(X.values)
y = np.matrix(y.values)
theta = np.matrix(np.array(np.zeros((cols-1, 1))))

# print('X \n', X)
print('X.shape = ', X.shape)
print('**************************************')
# print('y \n', y)
print('y.shape = ', y.shape)
print('**************************************')
# print('theta \n', theta)
print('theta.shape = ', theta.shape)
print('**************************************')

#%%
# Cost function
def computeCost(X, y, theta):
    J = np.sum(np.power(((X * theta) - y), 2)) / (2 * len(y))
    return float(J)

print('With theta = [0 : 0]\nCost computed =', computeCost(X, y, theta))
print('Expected cost value (approx) 32.07')
print('**************************************')
print('With theta = [-1 : 2]\nCost computed =', computeCost(X, y, np.array([[-1],[2]])))
print('Expected cost value (approx) 54.24')
print('**************************************')

#%%
# Running Gradient Descent
def gradientDescent(X, y, theta, alpha, num_iters):
    temp = np.matrix(np.zeros(theta.shape))
    J_history = np.zeros((num_iters, 1))

    for i in range(num_iters):
        for j in range(len(theta)):
            temp[j, 0] = theta[j, 0] - ((alpha / len(y)) * np.sum(np.multiply(((X * theta) - y), X[:, j])))

        theta = temp.copy()
        J_history[i] = computeCost(X, y, theta)

    return theta, J_history

iterations = 1500
alpha = 0.01

theta, J_history = gradientDescent(X, y, theta, alpha, iterations)

print('Theta found by gradient descent:\n', theta[0,0], '\n ', theta[1,0])
print('Expected theta values (approx)\n -3.6303\n  1.1664')
print('**************************************')
# print('J_history\n', J_history)
# print('**************************************')

#%%
# Plot the linear fit
plt.plot(X[:, 1], X*theta, 'r-', linewidth= 1.5, label= 'Linear regression')
plt.legend()

#%%
# Predict values for population sizes of 35,000 and 70,000
predict1 = [1., 3.5] * theta
print('For population = 35,000, we predict a profit of %f' %(predict1 * 10000))
predict2 = [1., 7.] * theta
print('For population = 70,000, we predict a profit of %f' %(predict2 * 10000))
print('**************************************')

#%%
# draw error graph
fig, ax = plt.subplots(figsize=(6,6))
ax.plot(np.arange(1, iterations+1), J_history, 'r', linewidth=2)
ax.set(xlabel= 'No. of Iterations', ylabel= 'Cost', title= 'Error vs. Training Epoch')

#%%
# Visualizing J(theta_0, theta_1)

# Grid over which we will calculate J
theta0_vals = np.linspace(-10, 10, 100)
theta1_vals = np.linspace(-1, 4, 100)

# initialize J_vals to a matrix of 0's
J_vals = np.zeros((len(theta0_vals), len(theta1_vals)))

# Fill out J_vals
for i in range(len(theta0_vals)):
    for j in range(len(theta1_vals)):
        t = np.array([[theta0_vals[i]], [theta1_vals[j]]])
        J_vals[i,j] = computeCost(X, y, t)

J_vals = J_vals.T
theta0_vals, theta1_vals = np.meshgrid(theta0_vals, theta1_vals)
fig, ax = plt.subplots(figsize= (7,7), subplot_kw={'projection' : '3d'})
ax.plot_surface(theta0_vals, theta1_vals, J_vals, cmap= 'jet')
# ax.view_init(0,90)
ax.set(xlabel= '$\mathrm{\\theta_{0}}$', ylabel= '$\mathrm{\\theta_{1}}$', zlabel= 'Cost Function  $\mathrm{J(\\theta)}$')
plt.plot(theta[0,0], theta[1,0], 'rx', markersize=10, linewidth=2)


# =============================================================================
# Funzioni varie su Lagrange
# =============================================================================

import numpy as np
from numpy import matlib

# For compunting the Lebesgue functions
def l_j(j, x, nodes):
    N = np.shape(nodes)[0]
    product = 1.0
    for i in range(0, N):
        if not i==j:
            product *= (x - nodes[i]) / (nodes[j] - nodes[i])
    return product

def lS_j(j, x, nodes,S):
    N = np.shape(nodes)[0]
    product = 1.0
    for i in range(0, N):
        if not i==j:
            product *= (S(x) - S(nodes[i])) / (S(nodes[j]) - S(nodes[i]))
    return product

#def y(x,nodes):
#    N = np.shape(nodes)[0]
#    csum = 0
#    for j in range(0, N):
#        csum += np.abs(l_j(j, x, nodes))
#    return csum

def drange(start, stop, step):
    r = start
    while r < stop:
        yield r
        r += step

#def d(nodes,interval):
#    greatest = None
#    for x in interval:
#        current = y(x,nodes)
#        if greatest is None or current > greatest:
#            greatest = current
#    return greatest

def lebesgue(x,xx):
    N = np.shape(x)[0]
    lsum = np.zeros(np.shape(xx)[0])
    for i in range(0, N):
        lsum += np.abs(l_j(i,xx, x))
    return lsum

def lagrange_interp(xx,x,y):
    n=len(x); m=len(xx)
    L=np.zeros((m,n))
    for k in range(n):
        x_k = np.concatenate((x[:k],x[k+1:]))
        L[:,k] = np.prod(np.matlib.repmat(xx.reshape(m,1),1,n-1) - np.matlib.repmat(x_k,m,1) , axis=1) / np.prod(x[k]-x_k)
    return np.dot(L,y)

def ChebyL(n):
    #compute the n Chebyshev-Lobatto points in [-1,1]
    return -np.cos(np.pi*np.arange(0.,n)/(n-1))

def Cheby(n):
    #compute the n Chebyshev-Lobatto points in [-1,1]
    return -np.cos(np.pi*(2*np.arange(0.,n)-1)/(2*n))


import random
import matplotlib
matplotlib.use('Agg')
from matplotlib import pyplot as plt
import math
import numpy as np
from statistics import mean
import sys

## Defining the ranges of the values used by us
total_points = 10**6
dim_range = [1,2,4,8,16,32,64]
batch = 100

## Generating the random points. Indexed by math.log2(dim)
random_all = []
for dim in dim_range:
	random_dim = []
	for i in range(total_points):
		random_point = [random.random() for _ in range(dim)]
		random_dim.append(random_point)
	random_all.append(random_dim)

## Generating the values of all the distances for all the dimensions, after taking the samples
d1_list = []
d2_list = []
dinfi_list = []
for dim in dim_range:
	print(int(math.log2(dim)))
	list_all = random_all[int(math.log2(dim))]
	list_sampled = random.sample(list_all, batch)

	d1_list_individual=[]
	d2_list_individual=[]
	dinfi_list_individual=[]

	for i in range(batch):

		temp_list_d1 = []
		temp_list_d2 = []
		temp_list_dinfi = []

		for j in range(total_points):

			point_1 = np.array(list_sampled[i])
			point_2 = np.array(list_all[j])

			if np.array_equal(point_1,point_2):
				continue

			d1 = np.linalg.norm((point_1 - point_2), ord=1)
			d2 = np.linalg.norm((point_1 - point_2), ord=2)
			dinfi = np.linalg.norm((point_1 - point_2), ord=np.inf)

			temp_list_d1.append(d1)
			temp_list_d2.append(d2)
			temp_list_dinfi.append(dinfi)

		min_d1 = min(temp_list_d1)
		max_d1 = max(temp_list_d1)

		min_d2 = min(temp_list_d2)
		max_d2 = max(temp_list_d2)

		min_dinfi = min(temp_list_dinfi)
		max_dinfi = max(temp_list_dinfi)

		d1_list_individual.append(max_d1*1./min_d1)
		d2_list_individual.append(max_d2*1./min_d2)
		dinfi_list_individual.append(max_dinfi*1./min_dinfi)

	## Getting the minimum and maximum values in these lists
	d1_list.append(mean(d1_list_individual))
	d2_list.append(mean(d2_list_individual))
	dinfi_list.append(mean(dinfi_list_individual))

# Making the plots and showing/storing them
plt.plot(dim_range,d1_list,'r',dim_range,d2_list,'b', dim_range, dinfi_list, 'g')
plt.xlabel('Dimensions of the points')
plt.ylabel('Average ratio of max to min distance')
plt.legend(['L1 dist','L2 dist','Linf dist'])

if sys.argv[1]=='log':
	plt.yscale('log')
	plt.title('Plot of average ratio v/s dimensions (Logarithmic y-scale)')
	plt.savefig('q1_plot_log.png')
else:
	plt.title('Plot of average ratio v/s dimensions (Normal y-scale)')
	plt.savefig('q1_plot.png')


# Basic imports
import numpy as np

# ----------------------------Scale independent metrics------------------------

# The mean absolute percentage error (MAPE), is a measure of prediction
# accuracy of a forecasting method in statistics. The 'min_val' variable is
# used to fill zeros in test data, if any. Filling zero with a constant helps
# to avoid division by zero error
def mean_absolute_percentage_error(y_true, y_pred, min_val=0):
    y_true = y_true + min_val
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

# SMAPE is an alternative for MAPE when there are zeros in the testing data. It
# scales the absolute percentage by the sum of forecast and observed values
def s_mean_absolute_percentage_error(y_true, y_pred):
    return np.mean(np.abs((y_true - y_pred) / ((y_true + y_pred)/2))) * 100

# MAE is used to measure how close the forecast is to observed value
def mean_absolute_error(y_true, y_pred):
    return np.mean(abs(y_true - y_pred))

# MPE represents the percentage of average error occurred, while forecasting.
# It shares similar properties of MAPE except MPE also shows the direction of
# error.
def mean_percentage_error(y_true, y_pred):
    return np.mean((y_true - y_pred) / y_true) * 100

# -----------------------------Scale dependent metrics-------------------------

# In statistics, the mean squared error (MSE) of an estimator measures the
# average of the squares of the errors. The squaring is necessary to remove any
# negative signs. It also gives more weight to larger differences
def mean_squared_error(y_true, y_pred):
    return np.mean((y_true - y_pred)**2)

# RMSE is the standard deviation of the residuals. It shares the same
# properties as MSE. The square root is used to dampen the magnitude of errors
# caused by squaring them.
def root_mean_squared_error(y_true, y_pred):
    return np.sqrt(np.mean((y_true - y_pred)**2))

# MFE is the difference between observed and forecasted value
def mean_forecast_error(y_true, y_pred):
    return np.mean(y_true - y_pred)

# It measures the total squared deviation of forecasted observations, from the
# observed values
def sum_of_squared_error(y_true, y_pred):
    return np.sum((y_true - y_pred)**2)

# It is same as MSE, except that here the original sign is kept for each
# individual squared error. SMSE panelizes extreme errors, occurred while
# forecasting.
def signed_mean_squared_error(y_true, y_pred):
    error = y_true-y_pred
    return np.mean((error/abs(error))*(error**2))

# NMSE normalizes the obtained MSE after dividing it by the test variance. It
# is a balanced error measure and is very effective in judging forecast
# accuracy of a model.
def normalised_mean_squared_error(y_true, y_pred):
    mse = mean_squared_error(y_true, y_pred)
    return mse / (np.sum((y_true - np.mean(y_true)) ** 2)/(len(y_true)-1))

# It is a normalized measure of total forecast error.
def theil_u_statistic(y_true, y_pred):
    error = y_true - y_pred
    mfe = np.sqrt(np.mean(y_pred**2))
    mse = np.sqrt(np.mean(y_true**2))
    rmse = np.sqrt(np.mean(error**2))
    return rmse / (mfe*mse)

# This evaluation metric is used to over come some of the problems of MAPE and
# is used to measure if the forecasting model is better than the naive model or
# not.
def mean_absolute_scaled_error(y_true, y_pred, naive_mod_mae):
    error = y_true - y_pred
    return np.mean(abs(error)/naive_mod_mae)


def circumsphere(vertices):
    '''
    @param vertices: an array of simplices
    @returns: radius of circumsphere, coordinates of circumsphere
    @rtype: tuple
    '''

    # based on https://westy31.home.xs4all.nl/Circumsphere/ncircumsphere.htm and
    # https://codereview.stackexchange.com/questions/77593/calculating-the-volume-of-a-tetrahedron

    from scipy.spatial import distance

    # distances
    squared_dists = distance.pdist(vertices, metric='sqeuclidean')
    n = distance.num_obs_y(squared_dists)

    # add ones and a zero to make a Cayley-Menger matrix.
    squared_dists_mat = distance.squareform(squared_dists)
    with_border = np.insert(np.insert(squared_dists_mat, 0, values=1, axis=1), 0, values=1, axis=0)
    np.fill_diagonal(with_border, 0)

    #the first diagonal element of its inverse holds -2*r*r
    #and first row / first column hold barycentric coordinates of the sphere
    inv = np.linalg.inv(with_border)
    r = math.sqrt(inv[0][0] / -2)
    barycentric_coodinates = inv[1:, 0]
    return r, bary2cart(vertices, barycentric_coodinates)

def bary2cart(simplex, point):
    simplex, point = np.asarray(simplex), np.asarray(point)
    return np.dot(simplex.T, point)

def check_point_in_sphere(sphere, r, point):
    '''
    @param sphere: coodinates of a shpere
    @type sphere: list
    @param r: raduius of a sphere
    @type r: float
    @param point: coordinates of a point to check
    @type point: list
    @returns: boolean value
    @rtype: bool
    '''
    sphere, point = np.asarray(sphere), np.asarray(point)
    if np.sum((sphere - point)**2) <= r ** 2:
        return True
    else:
        return False
'''
Example:
pentachoron = [[1, 1, 1, -1.0 / math.sqrt(5)], [1, -1, -1, -1.0 / math.sqrt(5)], [-1, 1, -1, -1.0 / math.sqrt(
    5)], [-1, -1, 1, -1.0 / math.sqrt(5)], [0, 0, 0, math.sqrt(5) - 1.0 / math.sqrt(5)]]
tetrahedron = [[1, 1, 1], [1, -1, -1], [-1, 1, -1], [-1, -1, 1]]
triangle = [(3, 3), (1, 3), (1, 1)]

print circumsphere(pentachoron)
print circumsphere(tetrahedron)
print circumsphere(triangle)'''


# %%
from pylab import *
from scipy.linalg import lstsq
from scipy.special import jn

# %% [markdown]
# ##### Constants

# %%
N = 101  # no of data points
K = 9    # no of sets of data with varying noise

A_true, B_true = 1.05, -0.105 # true values of A and B

DATAFILE = "fitting.dat"

# %% [markdown]
# ### 1. Generate the data points

# %%
""" run generate_data.py to generate the data """

# %% [markdown]
# ### 2. Load data

# %%
raw_data = loadtxt(DATAFILE)

Time = raw_data[:, 0]
F = raw_data[:, 1:]

# %% [markdown]
# ### 3. The function and the noise

# %%
def g(t, A=A_true, B=B_true):
    return A * jn(2, t) + B * t

F_true = g(Time)
Sigma = logspace(-1, -3, K)  # vector of stdevs of noise

# %% [markdown]
# ### 4. Plot the data

# %%
figure(figsize=(9, 7))
grid(True)
title("Q4: Data to be fitted to theory", size=16)
xlabel("$t$   $\longrightarrow$", size=16)
ylabel("$f(t)+noise$   $\longrightarrow$", size=16)
plot(Time, F)
plot(Time, F_true, color='black', lw=2)
legend([f"$\sigma_{i + 1}$ = {s:.3f}" for i, s in enumerate(Sigma)] + ["True Value"])
show()

# %% [markdown]
# ### 5. Plot the first column with error bars

# %%
figure(figsize=(9, 7))
grid(True)
title("Q5: Data points for $\sigma = 0.10$ along with exact function", size=16)
xlabel("$t$   $\longrightarrow$", size=16)
ylabel("$f(t)+noise$   $\longrightarrow$", size=16)
errorbar(Time[::5], F[::5, 0], Sigma[0], fmt="ro")
plot(Time, F_true, color='black', lw=2)
legend(["$f(t)$", "Errorbar"])
show()

# %% [markdown]
# ### 6. Confirm that the two vectors are equal

# %%
M = c_[jn(2, Time), Time]
assert allclose(F_true.reshape(N, 1), matmul(M, [[A_true], [B_true]]))

# %% [markdown]
# ### 7. Mean Squared Error for various A and B

# %%
# 0 to 2 in steps of 0.1 (including endpoint)
numA = int((2 - 0) / 0.1) + 1
A = linspace(0, 2, numA)

# -0.2 to 0 in steps of 0.01 (including endpoint)
numB = int((0 - -0.2) / 0.01) + 1
B = linspace(-0.2, 0, numB)

eps = zeros((numA, numB))
for i in range(numA):
    for j in range(numB):
        eps[i][j] = mean((F[:, 0] - g(Time, A[i], B[j])) ** 2)

# %% [markdown]
# ### 8. Plot the MSE

# %%
figure(figsize=(9, 7))
grid(True)
title("Q8: Contour plot of $\epsilon_{ij}$", size=16)
xlabel("$A$   $\longrightarrow$", size=16)
ylabel("$B$   $\longrightarrow$", size=16)
clabel(contour(A, B, eps, 15))
plot([A_true], [B_true], "ro")
annotate("Exact location", xy=(A_true, B_true), size=16)
show()

# %% [markdown]
# ### 9. Obtain best estimate of A and B

# %%
print("Best estimate:   A = {}, B = {}".format(*lstsq(M, F[:, 0])[0]))

# %% [markdown]
# ### 10. Plot the error in A and B for different stdev of noise

# %%
Aerr, Berr = abs(lstsq(M, F)[0] - [[A_true], [B_true]])

figure(figsize=(9, 7))
grid(True)
title("Q10: Variation of error with noise", size=16)
xlabel("$Noise$ $standard$ $deviation$   $\longrightarrow$", size=16)
ylabel("$Error$   $\longrightarrow$", size=16)
plot(Sigma, Aerr, 'o', linestyle="dashed")
plot(Sigma, Berr, 'o', linestyle="dashed")
legend(["Aerr", "Berr"])
show()

# %% [markdown]
# ### 11. Replot using log-log scale

# %%
figure(figsize=(9, 7))
grid(True)
title("Q11: Variation of error with noise", size=16)
xlabel("$\sigma_n$   $\longrightarrow$", size=16)
ylabel("$Error$   $\longrightarrow$", size=16)
xscale("log")
yscale("log")
errorbar(Sigma, Aerr, Sigma, fmt="o")
errorbar(Sigma, Berr, Sigma, fmt="o")
legend(["Aerr", "Berr"])
show()


import numpy as np

# linear normalization
def linear_normalization(matrix, types):
    """
    Normalize decision matrix using linear normalization method.

    Parameters
    -----------
        matrix : ndarray
            Decision matrix with m alternatives in rows and n criteria in columns
        types : ndarray
            Criteria types. Profit criteria are represented by 1 and cost by -1.

    Returns
    --------
        ndarray
            Normalized decision matrix

    Examples
    ----------
    >>> nmatrix = linear_normalization(matrix, types)
    """

    x_norm = np.zeros(np.shape(matrix))
    x_norm[:, types == 1] = matrix[:, types == 1] / (np.amax(matrix[:, types == 1], axis = 0))
    x_norm[:, types == -1] = np.amin(matrix[:, types == -1], axis = 0) / matrix[:, types == -1]
    return x_norm

# min-max normalization
def minmax_normalization(matrix, types):
    """
    Normalize decision matrix using minimum-maximum normalization method.

    Parameters
    -----------
        matrix : ndarray
            Decision matrix with m alternatives in rows and n criteria in columns
        types : ndarray
            Criteria types. Profit criteria are represented by 1 and cost by -1.

    Returns
    --------
        ndarray
            Normalized decision matrix

    Examples
    ----------
    >>> nmatrix = minmax_normalization(matrix, types)
    """

    x_norm = np.zeros((matrix.shape[0], matrix.shape[1]))
    x_norm[:, types == 1] = (matrix[:, types == 1] - np.amin(matrix[:, types == 1], axis = 0)
                             ) / (np.amax(matrix[:, types == 1], axis = 0) - np.amin(matrix[:, types == 1], axis = 0))

    x_norm[:, types == -1] = (np.amax(matrix[:, types == -1], axis = 0) - matrix[:, types == -1]
                           ) / (np.amax(matrix[:, types == -1], axis = 0) - np.amin(matrix[:, types == -1], axis = 0))

    return x_norm

# max normalization
def max_normalization(matrix, types):
    """
    Normalize decision matrix using maximum normalization method.

    Parameters
    -----------
        matrix : ndarray
            Decision matrix with m alternatives in rows and n criteria in columns
        types : ndarray
            Criteria types. Profit criteria are represented by 1 and cost by -1.

    Returns
    --------
        ndarray
            Normalized decision matrix

    Examples
    ----------
    >>> nmatrix = max_normalization(matrix, types)
    """

    maximes = np.amax(matrix, axis = 0)
    matrix = matrix / maximes
    matrix[:, types == -1] = 1 - matrix[:, types == -1]
    return matrix

# sum normalization
def sum_normalization(matrix, types):
    """
    Normalize decision matrix using sum normalization method.

    Parameters
    -----------
        matrix : ndarray
            Decision matrix with m alternatives in rows and n criteria in columns
        types : ndarray
            Criteria types. Profit criteria are represented by 1 and cost by -1.

    Returns
    --------
        ndarray
            Normalized decision matrix

    Examples
    ----------
    >>> nmatrix = sum_normalization(matrix, types)
    """

    x_norm = np.zeros((matrix.shape[0], matrix.shape[1]))
    x_norm[:, types == 1] = matrix[:, types == 1] / np.sum(matrix[:, types == 1], axis = 0)
    x_norm[:, types == -1] = (1 / matrix[:, types == -1]) / np.sum((1 / matrix[:, types == -1]), axis = 0)

    return x_norm

# vector normalization
def vector_normalization(matrix, types):
    """
    Normalize decision matrix using vector normalization method.

    Parameters
    -----------
        matrix : ndarray
            Decision matrix with m alternatives in rows and n criteria in columns
        types : ndarray
            Criteria types. Profit criteria are represented by 1 and cost by -1.

    Returns
    --------
        ndarray
            Normalized decision matrix

    Examples
    -----------
    >>> nmatrix = vector_normalization(matrix, types)
    """

    x_norm = np.zeros((matrix.shape[0], matrix.shape[1]))
    x_norm[:, types == 1] = matrix[:, types == 1] / (np.sum(matrix[:, types == 1] ** 2, axis = 0))**(0.5)
    x_norm[:, types == -1] = 1 - (matrix[:, types == -1] / (np.sum(matrix[:, types == -1] ** 2, axis = 0))**(0.5))

    return x_norm


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

import numpy as np

## Function to get the givens_rotation_Qk matrix for givens rotation
def givens_rotation_Qk(k,A):
    Q = np.eye(len(A))
    alfa_k = A[k,k]
    beta_k = A[k+1,k]
    ck = alfa_k/(np.sqrt((alfa_k**2)+(beta_k**2)))
    sk = -(beta_k)/(np.sqrt((alfa_k**2)+(beta_k**2)))
    Q[k,k] = ck
    Q[k,k+1] = -sk
    Q[k+1,k] = sk
    Q[k+1,k+1] = ck
    return Q

## Function which returns a matrix after a givens rotation
def QR_fatoration(A):
    R = A
    matrix_i_size = len(A)
    Q =np.eye(matrix_i_size)
    Q_iteration_size = matrix_i_size - 1
    for i in range (0, Q_iteration_size,1):
        Q = Q@np.transpose(givens_rotation_Qk(i,R))
        R = givens_rotation_Qk(i,R)@R
    return [R,Q]

def wilkinson_heuristics(A,n):
    get_sgnd = lambda d: 1 if d >= 0 else -1
    alfa_ant = A[n-2,n-2]
    alfa = A[n-1,n-1]
    beta = A[(n-2)+1,n-2]
    dk = (alfa_ant - alfa)/2
    uk = alfa + dk - get_sgnd(dk)*(np.sqrt((dk**2)+(beta**2)))
    return [uk,beta]

def QR_algorithm(A, erro = 1/1000000, spectral_shift = True):
    #round_parameter = len(str(int(1/erro)))
    A = A.copy()
    get_uk = lambda A,n,k,spectral_shift: wilkinson_heuristics(A,n)[0] if ((k > 0) and (spectral_shift == True)) else 0
    V = I = np.eye(len(A))
    k = 0
    for m in range(len(A),1,-1):
        beta = wilkinson_heuristics(A,m)[1]
        while(abs(beta)>erro):
            uk = get_uk(A,m,k,spectral_shift)
            R,Q = QR_fatoration(A - (uk*I))
            A = R@Q + uk*I
            V = V@Q
            k = k + 1
            beta = wilkinson_heuristics(A,m)[1]
        A = np.matrix.round(A,6) # A = np.matrix.round(A,round_parameter - 1)
    lamb = A
    return [V,lamb,k]

def test_qr_fatoration():
    #exemple 1:
    A = np.array([[2,1,0,0],
                [1,2,1,0],
                [0,1,2,1],
                [0,0,1,2]])

    R = QR_fatoration(A)[0]
    Q = QR_fatoration(A)[1]
    print("R = ", R)
    print("Q = ", Q)

def test_qr_algorithm():
    #exemple 1:
    A = np.array([[2,1,0,0],
                [1,2,1,0],
                [0,1,2,1],
                [0,0,1,2]])
    print("test QR_algorithm with spectral_shift")
    V,lamb,k = QR_algorithm(A)
    print("V = \n", V)
    print("lamb = \n",lamb)
    print("k shift = ", k)
    new_A = V@lamb@np.transpose(V)
    #new_A = np.matrix.round(new_A,6)
    print("\n")
    print("A = \n", A)
    print("V Λ V_transposto = \n", new_A)

    print("\n\n")

    print("test QR_algorithm without spectral_shift")
    V,lamb,k = QR_algorithm(A,spectral_shift = False)
    print("V = \n", V)
    print("lamb = \n",lamb)
    print("k = ", k)
    new_A = V@lamb@np.transpose(V)
    #new_A = np.matrix.round(new_A,6)
    print("\n")
    print("A = \n", A)
    print("V Λ V_transposto = \n", new_A)

if __name__ == "__main__":
    try:
        while True:
            test = input("Do you want to test the QR_algorithm(1) or QR_fatoration(0): ")
            if (test == "1"):
                test_qr_algorithm()
                print("\n")
            elif (test == "0"):
                test_qr_fatoration()
                print("\n")
            else:
                print("your input isn't valid \nplease, try again\n")

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


import random
import math
from typing import Optional

import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, Aer, transpile, assemble
from qiskit.visualization import plot_histogram
from sympy import Rational
from sympy.ntheory.continued_fraction import continued_fraction, continued_fraction_convergents

from qft import qft
from elementary import ax_modM

def shor(M: int, a: Optional[int] = None, N_len: Optional[int] = None, show_hist: bool = True, use_only_period: bool = False):
    if a is None:
        random.randint(2, M - 1)
    gcd = math.gcd(a, M)
    if gcd != 1:
        print(f'Answer is found in only classical computation: {gcd}')
        return gcd

    if N_len is None:
        N_len = int(np.ceil(np.log2(M ** 2)))
    N = 2 ** N_len
    qc = QuantumCircuit(10 * N_len - 2, N_len)

    qc.h(range(N_len))

    qc.append(ax_modM(a=a, M=M, N_len=N_len), range(10 * N_len - 2))

    qc.append(qft(n=N_len), range(N_len))

    qc.measure(range(N_len), range(N_len))

    backend = Aer.get_backend('aer_simulator_matrix_product_state')#('aer_simulator')
    qc = transpile(qc, backend)
    job = backend.run(qc, shots=10000)
    hist = job.result().get_counts()

    if show_hist:
        plot_histogram(hist)
        plt.show()

    print(sorted(hist.items(), key=lambda x: x[1], reverse=True))
    y_list = []
    for measured_key, _ in sorted(hist.items(), key=lambda x: x[1], reverse=True):
        y = int(measured_key[-N_len:], 2)
        if y == 0:
            continue

        for fraction in continued_fraction_convergents(continued_fraction(Rational(y, N))):
            maybe_r = fraction.denominator
            if maybe_r != 1 and maybe_r < M and a ** maybe_r % M == 1:
                if use_only_period:
                    return maybe_r

                if maybe_r % 2 == 1 or (a ** (maybe_r // 2) + 1) % M == 0:
                    continue
                gcd = math.gcd(a ** (maybe_r // 2) + 1, M)
                if gcd != 1:
                    return gcd
                gcd = math.gcd(a ** (maybe_r // 2) - 1, M)
                if gcd != 1:
                    return gcd

    print('gcd was not found?!')

if __name__ == '__main__':
    print(shor(M=8, a=3))
    #print(shor(M=57, a=5))
    #print(shor(M=7, a=3, N_len=3, use_only_period=True))
    #print(shor(M=7, a=3, use_only_period=True))


import scipy
from scipy import signal
import numpy as np
import cv2
from matplotlib import pyplot as plt
import os
import matplotlib.colors as colors

def read_img(path):
    return cv2.imread(path, 0)

def save_img(img, path):
    cv2.imwrite(path,img)
    print(path, "is saved!")

def display_img(img):
    plt.imshow(img, cmap='gray', interpolation='bicubic')
    plt.xticks([]), plt.yticks([])  # to hide tick values on X and Y axis
    plt.show()

def convolve(image, kernel):
    # Return the convolution result: image * kernel.
    # Input- image: H x W
    #        kernel: h x w
    # Output- convolve: H x W
    output = signal.convolve2d(image, kernel, boundary='symm', mode = 'same')
    # TODO: You can use the function scipy.signal.convolve2d().
    return output

def edge_detection(image):
    # Return the gradient magnitude of the input image
    # Input- image: H x W
    # Output- grad_magnitude: H x W

    # TODO: Fix kx, ky
#    kx = 0.5*np.array([[0,0,0],[1,0,-1],[0,0,0]])  # 1 x 3
#    ky = 0.5*np.array([[0,1,0],[0,0,0],[0,-1,0]])  # 3 x 1
    kx = 0.5*np.array([[1,0,-1]])
    ky = 0.5*np.array([[1],[0],[-1]])

    Ix = convolve(image, kx)
    Iy = convolve(image, ky)

    # TODO: Use Ix, Iy to calculate grad_magnitude
    grad_magnitude = np.sqrt(Ix**2 + Iy**2)

    return grad_magnitude, Ix, Iy

def sobel_operator(image):
    # Return Gx, Gy, and the gradient magnitude.
    # Input- image: H x W
    # Output- Gx, Gy, grad_magnitude: H x W

    # TODO: Use convolve() to complete the function
    Gx, Gy, grad_magnitude = None, None, None
    kx = np.array([[1,0,-1],[2,0,-2],[1,0,-1]])  # 1 x 3
    ky = np.array([[1,2,1],[0,0,0],[-1,-2,-1]])  # 3 x 1

    Gx = convolve(image, kx)
    Gy = convolve(image, ky)

    grad_magnitude = np.sqrt(Gx**2 + Gy**2)
    return Gx, Gy, grad_magnitude

def steerable_filter(image, angles=[0, np.pi/6, np.pi/3, np.pi/2, np.pi*2/3, np.pi*5/6]):
    # Given a list of angels used as alpha in the formula,
    # return the corresponding images based on the formula given in pdf.
    # Input- image: H x W
    #        angels: a list of scalars
    # Output- results: a list of images of H x W
    # You are encouraged not to use sobel_operator() in this function.

    # TODO: Use convolve() to complete the function
    output = []
    for i in range(len(angles)):
        kernal = kernal_steerable_filter(angles[i])
        output.append(convolve(image, kernal))
    return output

def kernal_steerable_filter(alpha):
    return np.array([[np.cos(alpha)+np.sin(alpha), 2*np.sin(alpha), -np.cos(alpha)+np.sin(alpha)],
                      [2*np.cos(alpha), 0 , -2*np.cos(alpha)],
                      [np.cos(alpha)-np.sin(alpha), -2*np.sin(alpha), -np.cos(alpha)-np.sin(alpha)]])

def image_patches(image, patch_size=(16,16)):
    # Given an input image and patch_size,
    # return the corresponding image patches made
    # by dividing up the image into patch_size sections.
    # Input- image: H x W
    #        patch_size: a scalar tuple M, N
    # Output- results: a list of images of size M x N

    # TODO: Use slicing to complete the function
    if (len(image.shape)==2):
        m,n = image.shape
    else:
        m, n, _ = image.shape
    h_num = m//patch_size[0]
    w_num = n//patch_size[1]
    output = []
    for i in range(h_num):
        for j in range(w_num):
            h_start, h_end = i*patch_size[0], (i+1)*patch_size[0]
            w_start, w_end = j*patch_size[1], (j+1)*patch_size[1]
            if (len(image.shape)==2):
                output.append(image[h_start:h_end, w_start:w_end])
            else:
                output.append(image[h_start:h_end, w_start:w_end, :])
    return output

def gaussian(x,y,sigma):
    return 1/(2*np.pi*sigma)*np.exp(-(x**2+y**2)/(2*sigma))

def gaussian_kernal(sigma, kernal_size=(3,3)):
    offset_h=kernal_size[0]//2
    offset_w=kernal_size[1]//2
    result = np.ones(kernal_size)
    for i in range(kernal_size[0]):
        for j in range(kernal_size[1]):
            result[i,j] = gaussian(i-offset_h, j-offset_w, sigma)
    return result

def gaussian1d(x, sigma):
    return 1/np.sqrt(2*np.pi*sigma) * np.exp(-x**2/(2*sigma))

def gaussian1d_prime(x, sigma):
    return -1*x/(sigma*np.sqrt(2*np.pi*sigma)) * np.exp(-x**2/(2*sigma))

def gaussian1d_prime_prime(x, sigma):
    return -1/(sigma*np.sqrt(2*np.pi*sigma)) * (np.exp(-x**2/(2*sigma))-x**2/sigma*np.exp(-x**2/(2*sigma)))

def main():
    # The main function
    ########################
    img = read_img('./grace_hopper.png')

    ##### Image Patches #####
    if not os.path.exists("./image_patches"):
        os.makedirs("./image_patches")

    # Q1
    patch_size = (16,16)
    patches = image_patches(img, patch_size)
    # TODO choose a few patches and save them
    chosen_patches = patches[200];
    save_img(chosen_patches, "./image_patches/q1_patch.png")
#    display_img(chosen_patches)

    # Q2: No code

    ##### Gaussian Filter #####
    if not os.path.exists("./gaussian_filter"):
        os.makedirs("./gaussian_filter")

    # Q1: No code

    # Q2

    # TODO: Calculate the kernel described in the question.  There is tolerance for the kernel.
    sigma = 1/(2*np.log(2))
    kernel_gaussian = gaussian_kernal(sigma,(3,3))

    filtered_gaussian = convolve(img, kernel_gaussian)
    save_img(filtered_gaussian, "./gaussian_filter/q2_gaussian.png")

    # Q3
    edge_detect, _, _ = edge_detection(img)
    save_img(edge_detect, "./gaussian_filter/q3_edge.png")
    edge_with_gaussian, _, _ = edge_detection(filtered_gaussian)
    save_img(edge_with_gaussian, "./gaussian_filter/q3_edge_gaussian.png")
#    display_img(edge_detect)
    print("Gaussian Filter is done. ")

    ########################

    ##### Sobel Operator #####
    if not os.path.exists("./sobel_operator"):
        os.makedirs("./sobel_operator")

    # Q1: No code

    # Q2
    Gx, Gy, edge_sobel = sobel_operator(img)
    save_img(Gx, "./sobel_operator/q2_Gx.png")
    save_img(Gy, "./sobel_operator/q2_Gy.png")
    save_img(edge_sobel, "./sobel_operator/q2_edge_sobel.png")

    # Q3
    steerable_list = steerable_filter(img)
    for i, steerable in enumerate(steerable_list):
        save_img(steerable, "./sobel_operator/q3_steerable_{}.png".format(i))

    print("Sobel Operator is done. ")
    ########################

    #####LoG Filter#####
    if not os.path.exists("./log_filter"):
        os.makedirs("./log_filter")

    # Q1
    kernel_LoG1 = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]])
    kernel_LoG2 = 0.04*np.array([
        [0, 0, 3, 2, 2, 2, 3, 0, 0],
        [0, 2, 3, 5, 5, 5, 3, 2, 0],
        [3, 3, 5, 3, 0, 3, 5, 3, 3],
        [2, 5, 3, -12, -23, -12, 3, 5, 2],
        [2, 5, 0, -23, -40, -23, 0, 5, 2],
        [2, 5, 3, -12, -23, -12, 3, 5, 2],
        [3, 3, 5, 3, 0, 3, 5, 3, 3],
        [0, 2, 3, 5, 5, 5, 3, 2, 0],
        [0, 0, 3, 2, 2, 2, 3, 0, 0]
    ])
    filtered_LoG1 = convolve(img, kernel_LoG1)
    save_img(filtered_LoG1, "./log_filter/q1_LoG1.png")
    filtered_LoG2 = convolve(img, kernel_LoG2)
    save_img(filtered_LoG2, "./log_filter/q1_LoG2.png")

    print(img.shape)
    # Q2: No code
#    sigma = 1/(2*np.log(2))
    sigma = 2
    sigma1 = sigma/2.5
    sigma2 = sigma*2.5
    x = np.linspace(-10,10,200)
    y = gaussian1d_prime_prime(x, sigma)
    y1 = gaussian1d(x, sigma1)
    y2 = gaussian1d(x, sigma2)
#    plt.figure()
#    plt.plot(x,sigma*y,'b',label = 'LOG')
#    plt.plot(x, (y2-y1),'r', label = 'DoG')
#    plt.legend()

    print("LoG Filter is done. ")
    ########################

if __name__ == "__main__":
    main()


from math import factorial
import numpy as np

def pretty_polynomial(polynomial):
    """
    Takes a list of coefficients (must be in ascending powers of x) and produces a 'pretty' output.
    :param polynomial: the list of coefficients of a polynomial
    :return: a string of the polynomial in a more readable format.
    """
    s = ''
    for i in range(1, len(polynomial)):
        if polynomial[i] > 0:
            s += ' + ' + str(polynomial[i]) + 'x^' + str(i)
        elif polynomial[i] < 0:
            s += '  ' + str(polynomial[i]) + 'x^' + str(i)

    # If the constant is nonzero, prepend it to the string. Otherwise, get rid of the '+'.
    if polynomial[0]:
        s = str(polynomial[0]) + s
    else:
        s = s[2:]
    return s

def legendre_polynomial(n: int):
    """
    Calculate the coefficients of the nth order Legendre polynomial in x, using
    the Rodrigues formula: P_n(x) = 1/(2^n n!) (d/dx)^n [(x^2 - 1)^n].

    We will treat polynomials as lists in python, where the index in the list corresponds to the power
    of x, so the element in the list contains the coefficient of x^i. Thus our polynomials will be
    written in ascending powers of x, somewhat contrasting convention. All our operations will therefore
    be reduced to list methods.

    :param n: the order of the Legendre polynomial whose coefficients are to be determined
    :return: a list of coefficients in ascending powers of x.
    """
    # 1. Expand (x^2 - 1) ^ n with the binomial theorem.
    expansion = [None] * (n + 1)
    for i in range(len(expansion)):
        expansion[i] = (-1)**(n - i) * factorial(n) / (factorial(i) * factorial(n - i))

    # Because the brackets contain x^2, the expansion will only have even powers of x.
    # However, we need the odd powers too (which are all zero). 2n + 1 terms in total.
    coeffs = [None] * (2*n + 1)
    coeffs[::2] = expansion
    coeffs[1::2] = [0] * n

    # 2. Differentiate n times. Differentiating is the same as multiplying each element
    # by its index (remember this is the power of x), then deleting the first term in the list.
    for _ in range(n):
        coeffs = [i * coeffs[i] for i in range(1, len(coeffs))]

    # Don't forget the normalising constant in the Rodrigues formula.
    # The below code is the same as [x / (2**n * factorial(n)) for x in coeffs].
    return list(map(lambda x: x / (2**n * factorial(n)), coeffs))

def legendre_representation(poly):
    """
    Any polynomial can be written in terms of Legendre polynomials.
    :param poly: A list or np array of coefficients of the polynomial
    :return: A list of coefficients of the Legendre polynomials,
    e.g [3, 1, 0, 4] <=> 3P_0(x) + P_1(x) + 4P_3(x)
    """
    if not isinstance(poly, np.ndarray):
        poly = np.array(poly)
    n = len(poly) - 1
    legendre_coefficients = [0] * (n + 1)

    while n >= 0:
        Pn = legendre_polynomial(n)
        legendre_coefficients[n] = poly[n]/Pn[n]
        if n == len(poly) - 1:
            poly = poly - [legendre_coefficients[n] * x for x in Pn]
        else:
            poly[:n+1] = poly[:n+1] - [legendre_coefficients[n] * x for x in Pn]
        n -= 1

    return legendre_coefficients