import numpy as np
from operator import itemgetter

def kruskal(nodes,A):
    group=createDict(nodes)
    sortedEdges=A.take(A[:,2].astype(int).argsort(),axis=0)
    s=len(nodes)
    i=0
    j=0
    edges=np.empty((s-1,3),dtype='|S2')
    while j<s-1:
        if group.find(sortedEdges[i,0])!=group.find(sortedEdges[i,1]):
            edges[j,:]=sortedEdges[i]
            group.union(group.find(sortedEdges[i,0]),group.find(sortedEdges[i,1]))
            j=j+1
        i=i+1
    return edges

def createDict(nodes):
    group=DisjointSet()
    s=len(nodes)
    for i in nodes:
        group.add(i)
    return group

#This is a solution to problem about disjoint sets that comes from http://programmingpraxis.com/

class DisjointSet(dict):
    def add(self, item):
        self[item] = item

    def find(self, item):
        parent = self[item]

        while self[parent] != parent:
            parent = self[parent]

        self[item] = parent
        return parent

    def union(self, item1, item2):
        self[item2] = self[item1]

nodes = list( "ABCDEFG" )
edges = [ ("A", "B", 7), ("A", "D", 5),
          ("B", "C", 8), ("B", "D", 9), ("B", "E", 7),
      ("C", "E", 5),
      ("D", "E", 15), ("D", "F", 6),
      ("E", "F", 8), ("E", "G", 9),
      ("F", "G", 11)]

kruskal(nodes,np.array(edges))

def prims(nodes,edges):
    def change(t):
        for q in list(canid):
            if q[0]==t or q[1]==t:
                canid.remove(q)

        for q in list(m):
            if q[0]==t or q[1]==t:
                m.remove(q)
                canid.append(q)
        return
    m=list(edges)
    sol=[]
    canid=[]
    nadded=[]
    temp=min(m,key=itemgetter( 2 ))
    sol.append(temp)
    change(temp[0])
    change(temp[1])
    nadded.append(temp[0])
    nadded.append(temp[1])
    sz = len(nodes) - 2
    for i in xrange(sz):
        temp=min(canid,key=itemgetter( 2 ))
        sol.append(temp)
        if nadded.count(temp[0])==0:
            change(temp[0])
            nadded.append(temp[0])
        else:
            change(temp[1])
            nadded.append(temp[1])
    return sol

prims(nodes,edges)

def formChanger(oldData):
    newData=[]
    for i in oldData:
        newData.append((i[0],i[1],int(i[2])))
    return newData

q=np.load("MSTdata.npy")
edges=formChanger(q)
nodes = list( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcd")

kruskal(nodes,np.array(edges))
prims(nodes,edges)


# Laboratório Computacional 2

No laboratório computacional, você praticará o que aprendeu. Resolva os problemas com o auxílio do Python pesquisando apenas as informações essenciais de que precisa. Não use respostas prontas.

**Problema:** Uma fórmula usada para modelar o crescimento populacional é a função exponencial $P(t) = Ce^{rt}$, onde $C$ é a população inicial, $r$ a *taxa de crescimento* e $t$ o tempo decorrido. De acordo com o [[IBGE]](https://cidades.ibge.gov.br/brasil/pb), a população estimada no estado da Paraíba no último censo em 2010 era de 3.766.528 pessoas. Em 2019, este numéro saltou para 4.018.127 pessoas. Diante disso, crie um modelo simbólico para responder os itens a seguir:

- considerando o interstício de 2010 a 2019, qual foi a taxa de crescimento $r$ do estado?

- supondo que a população cresça com na mesma taxa encontrada no item anterior, qual será a população do estado em 2037? E em 2055?

**Problema:** O autor do código

```python
def minha_equacao_simbolica_legal():
    ...
    x = symbols('x')
    ...
    return (1/2*x^2 + 2*tg(x) + 3/4)^(3/2)
```

gostaria de obter o seguinte resultado simbólico

$$\left(\frac{\left(y + 2\right)^{2}}{2} + 2 \tan{\left(y + 2 \right)} + 0.75\right)^{1.5}$$

Porém, ele está com dificuldades. Ajude-o a detectar os problemas e corrija o código para que a função retorne a expressão desejada.

def minha_equacao_simbolica_legal():
    from sympy import symbols, tan
    x, y = symbols('x y')
    f = (x**2/2 + 2*tan(x) + 3/4)**(3/2)
    return f.subs(x,y+2)
minha_equacao_simbolica_legal()

**Problema:** Suponha que você é um cientista de dados trabalhando em um projeto que busca compreender como a iluminação de um ambiente afeta o desempenho de leitura em crianças na primeira infância.

A *iluminância* é uma grandeza física que mede o fluxo luminoso total sobre uma superfície por metro quadrado, isto é, a quantidade de luz incidente que ilumina a superfície. A fórmula de cálculo da iluminância produzida por um raio luminoso é dada por

$$E = \dfrac{I\cos(\theta)}{d^2},$$

onde $E$ é a iluminância, medida em $lux$ ou $lm/m^2$ (1 lux = 1 lúmen por metro quadrado), $I$ é o fluxo luminoso, medido em $lm$ (lúmen), $\theta$ é o ângulo entre o raio luminoso e a perpendicular à superfície no ponto de incidência, medido em graus, e $d$ é a distância entre a fonte luminosa e o ponto de incidência ("comprimento" do raio luminoso), medida em $m$ (metro). Quando a fonte está posta perpendicularmente ao ponto de incidência, a fórmula acima reduz-se a $E = I/d^2$.

A figura abaixo exemplifica os casos para $\theta = 0$ e $\theta = 45^{\circ}$.

- Sabendo que 100 lúmens = 1 Watt (W), prencha a tabela a seguir com os valores de $E$ correspondentes para quatro diferentes  potências de lâmpadas de LED e ângulos de incidência da fonte luminosa considerando $d = 2.5 \, m$. O valor para 6 W e 0 grau está preenchido como exemplo.

| LED | 0 | 30 | 45 | 60 |
|---|---|---|---|---|
|6 W| 96 lux  |
|8 W|
|10 W|
|12 W|

- Com a fórmula acima, é possível descobrir o percentual de iluminância que se ganha ou perde ao se inclinar a fonte luminosa a partir de 0 grau. Por exemplo, a iluminância a 45 graus é aproximadamente 71% superior em relação aquela obtida a 0 grau. Estime o percentual de iluminância relativo a 0 grau para os ângulos da tabela a seguir.

|ângulo | % |
|---|---|
| 0 | - |
| 36 | |
| 45 | 71|
| 72 |
| 88 |

**Obervações:**

- use livremente computação simbólica, funções normais, funções embutidas ou anônimas e/ou substituições como quiser.
- certifique-se de que os ângulos foram convertidos para radianos a fim de usar as funções do Python corretamente.

""" solucao
I = 6 theta = 0 iluminancia = 96.0000000000000 lux
I = 6 theta = 30 iluminancia = 48.0*sqrt(3) lux
I = 6 theta = 45 iluminancia = 48.0*sqrt(2) lux
I = 6 theta = 60 iluminancia = 48.0000000000000 lux
I = 8 theta = 0 iluminancia = 128.000000000000 lux
I = 8 theta = 30 iluminancia = 64.0*sqrt(3) lux
I = 8 theta = 45 iluminancia = 64.0*sqrt(2) lux
I = 8 theta = 60 iluminancia = 64.0000000000000 lux
I = 10 theta = 0 iluminancia = 160.000000000000 lux
I = 10 theta = 30 iluminancia = 80.0*sqrt(3) lux
I = 10 theta = 45 iluminancia = 80.0*sqrt(2) lux
I = 10 theta = 60 iluminancia = 80.0000000000000 lux
I = 12 theta = 0 iluminancia = 192.000000000000 lux
I = 12 theta = 30 iluminancia = 96.0*sqrt(3) lux
I = 12 theta = 45 iluminancia = 96.0*sqrt(2) lux
I = 12 theta = 60 iluminancia = 96.0000000000000 lux
""";


import numpy as np

a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

b = np.arange(12).reshape(4, 3).T
print(b)
# [[ 0  3  6  9]
#  [ 1  4  7 10]
#  [ 2  5  8 11]]

a_compare = a < b
print(a_compare)
# [[False  True  True  True]
#  [False False  True  True]
#  [False False False False]]

print(type(a_compare))
# <class 'numpy.ndarray'>

print(a_compare.dtype)
# bool

print(a > b)
# [[False False False False]
#  [ True  True False False]
#  [ True  True  True False]]

print(a <= b)
# [[ True  True  True  True]
#  [False False  True  True]
#  [False False False  True]]

print(a >= b)
# [[ True False False False]
#  [ True  True False False]
#  [ True  True  True  True]]

print(a == b)
# [[ True False False False]
#  [False False False False]
#  [False False False  True]]

print(a != b)
# [[False  True  True  True]
#  [ True  True  True  True]
#  [ True  True  True False]]

b_float = b.astype(float)
print(b_float)
# [[ 0.  3.  6.  9.]
#  [ 1.  4.  7. 10.]
#  [ 2.  5.  8. 11.]]

print(b_float.dtype)
# float64

print(a == b_float)
# [[ True False False False]
#  [False False False False]
#  [False False False  True]]

b_1d = np.arange(4, 8)
print(b_1d)
# [4 5 6 7]

print(a < b_1d)
# [[ True  True  True  True]
#  [False False False False]
#  [False False False False]]

print(a < 6)
# [[ True  True  True  True]
#  [ True  True False False]
#  [False False False False]]

print(a % 2)
# [[0 1 0 1]
#  [0 1 0 1]
#  [0 1 0 1]]

print(a % 2 == 0)
# [[ True False  True False]
#  [ True False  True False]
#  [ True False  True False]]

print(np.count_nonzero(a < 6))
# 6

print(np.all(a < 6))
# False

print(np.all(a < 6, axis=1))
# [ True False False]

print(np.any(a < 6))
# True

print(np.any(a < 6, axis=1))
# [ True  True False]

a_nan = np.array([0, 1, np.nan])
print(a_nan)
# [ 0.  1. nan]

print(a_nan == np.nan)
# [False False False]

print(np.isnan(a_nan))
# [False False  True]

print(a_nan > 0)
# [False  True False]
#
# /usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in greater
#   """Entry point for launching an IPython kernel.

a_nan_only = np.array([np.nan])
print(a_nan_only)
# [nan]

print(a_nan_only > 0)
# [False]

print(np.nan > 0)
# False

x = 6

print(4 < x < 8)
# True

# print(4 < a < 8)
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

print((a > 4) & (a < 8))
# [[False False False False]
#  [False  True  True  True]
#  [False False False False]]

# print((a > 4) and (a < 8))
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

# print(a > 4 & a < 8)
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

x = 6
y = 6
z = 6

print(x == y == z)
# True

c = np.zeros((3, 4), int)
print(c)
# [[0 0 0 0]
#  [0 0 0 0]
#  [0 0 0 0]]

# print(a == b == c)
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

print((a == b) & (b == c))
# [[ True False False False]
#  [False False False False]
#  [False False False False]]


import numpy as np

def mult_matrix(M, N):
    """Multiply square matrices of same dimension M and N"""
    # Converts N into a list of tuples of columns
    tuple_N = zip(*N)

    # Nested list comprehension to calculate matrix multiplication
    return [[sum(el_m * el_n for el_m, el_n in zip(row_m, col_n)) for col_n in tuple_N] for row_m in M]

def QR(A):
    m, n = A.shape
    Q = np.eye(m)
    for i in range(n - (m == n)):
        H = np.eye(m)
        H[i:, i:] = Householder(A[i:, i])
        Q = np.dot(Q, H)  # Create Q
        A = np.dot(H, A)  # Create R
    return Q, A

def Householder(a):
    v = a / (a[0] + np.copysign(np.linalg.norm(a), a[0]))
    v[0] = 1
    H = np.eye(a.shape[0])
    H -= (2 / np.dot(v, v)) * np.dot(v[:, None], v[None, :])
    return H

def SolveQR(a, b):
    q, r = QR(a)
    _, n = r.shape
    return np.linalg.solve(r[:n, :], np.dot(q.T, b)[:n])

a = np.array([[2, 3, 1],
              [5, 1, 2],
              [2, 8, 1]])
b = np.array([6, 8, 11])
Q, R = QR(a)
Q = Q.round(6)
R = R.round(6)
qq, rr = np.linalg.qr(a)
print("Q:")
print(Q)
print(qq)
print("R:")
print(R)
print(rr)

x = SolveQR(a,b)
print("X: ",x)


import numpy as np
import utils
import math
import scipy
from scipy import optimize
import random
from scipy.special import xlogy

class LogisticRegressor:

    def __init__(self):
        self.theta = None

    def train(self,X,y,num_iters=1000):

        """
        Train a linear model using scipy's function minimization.

        Inputs:
        - X: N X D array of training data. Each training point is a D-dimensional
         row.
        - y: 1-dimensional array of length N with values in the reals.
        - num_iters: (integer) number of steps to take when optimizing

        Outputs:
        - optimal value for theta
        """

        num_train,dim = X.shape

        # standardize X so that each column has zero mean and unit variance
        # remember to take out the first column and do the feature normalize

        X_without_1s = X[:,1:]
        X_norm, mu, sigma = utils.std_features(X_without_1s)

        # add the ones back and assemble the XX matrix for training

        XX = np.vstack([np.ones((X_norm.shape[0],)),X_norm.T]).T
        theta = np.zeros((dim,))

        # Run scipy's fmin algorithm to run gradient descent

        theta_opt_norm = scipy.optimize.fmin_bfgs(self.loss, theta, fprime = self.grad_loss, args=(XX,y),maxiter=num_iters)

        # convert theta back to work with original X
        theta_opt = np.zeros(theta_opt_norm.shape)
        theta_opt[1:] = theta_opt_norm[1:]/sigma
        theta_opt[0] = theta_opt_norm[0] - np.dot(theta_opt_norm[1:],mu/sigma)

        return theta_opt

    def loss(self, *args):
        """
        Compute the logistic loss function

        Inputs:
        - X: N x D array of data; each row is a data point.
        - y: 1-dimensional array of length N with real values.

        Returns: loss as a single float
        """
        theta,X,y = args
        m,dim = X.shape
        J = 0

        ##########################################################################
        # Compute the loss function for unregularized logistic regression        #
        # TODO: 1-2 lines of code expected                                       #
        ##########################################################################
        h = utils.sigmoid(X@theta)
        J = -1.0 / m * (y.T@np.log(h) + (1 - y).T@np.log(1 - h))
        ###########################################################################
        #                           END OF YOUR CODE                              #
        ###########################################################################
        return J

    def grad_loss(self, *args):
        """
        Compute the gradient logistic loss function

        Inputs:
        - X: N x D array of data; each row is a data point.
        - y: 1-dimensional array of length N with real values.

        Returns:  gradient with respect to theta; an array of the same shape as theta
        """
        theta,X,y = args
        m,dim = X.shape
        grad = np.zeros((dim,))

        ##########################################################################
        # Compute the gradient of the loss function for unregularized logistic   #
        # regression                                                             #
        # TODO: 1 line of code expected                                          #
        ##########################################################################
        grad = 1.0 / m * (X.T@(utils.sigmoid(X@theta) - y.T))
        ###########################################################################
        #                           END OF YOUR CODE                              #
        ###########################################################################
        return grad

    def predict(self, X):
        """
        Use the trained weights of this linear classifier to predict probabilities for
        data points.

        Inputs:
        - X: N x D array of training data. Each row is a D-dimensional point.

        Returns:
        - y_pred: Predicted output for the data in X. y_pred is a 1-dimensional
          array of length N, and each element is the probability of being in class 1
        """
        y_pred = np.zeros(X.shape[0])

        ###########################################################################
        # Compute the predicted outputs for X                                     #
        # TODO: 1 line of code expected                                           #
        ###########################################################################
        y_pred = np.round(utils.sigmoid(X@self.theta))
        ###########################################################################
        #                           END OF YOUR CODE                              #
        ###########################################################################
        return y_pred

class RegLogisticRegressor:

    def __init__(self) -> object:
        self.theta = None

    def train(self,X,y,reg=1e-5,num_iters=1000,norm=True):

        """
        Train a linear model using scipy's function minimization.

        Inputs:
        - X: N X D array of training data. Each training point is a D-dimensional
         row.
        - y: 1-dimensional array of length N with values in the reals.
        - reg: (float) regularization strength.
        - num_iters: (integer) number of steps to take when optimizing
        - norm: a boolean which indicates whether the X matrix is standardized before
                solving the optimization problem

        Outputs:
        - optimal value for theta
        """

        num_train,dim = X.shape

        # standardize features if norm=True

        if norm:
            # take out the first column and do the feature normalize
            X_without_1s = X[:,1:]
            X_norm, mu, sigma = utils.feature_normalize(X_without_1s)
            # add the ones back
            XX = np.vstack([np.ones((X_norm.shape[0],)),X_norm.T]).T
        else:
            XX = X

        # initialize theta
        theta = np.zeros((dim,))

        # Run scipy's fmin algorithm to run gradient descent
        theta_opt_norm = scipy.optimize.fmin_bfgs(self.loss, theta, fprime = self.grad_loss, args=(XX,y,reg),maxiter=num_iters)

        if norm:
            # convert theta back to work with original X
            theta_opt = np.zeros(theta_opt_norm.shape)
            theta_opt[1:] = theta_opt_norm[1:]/sigma
            theta_opt[0] = theta_opt_norm[0] - np.dot(theta_opt_norm[1:],mu/sigma)
        else:
            theta_opt = theta_opt_norm

        return theta_opt

    def loss(self, *args):
        """
        Compute the logistic loss function

        Inputs:
        - X: N x D array of data; each row is a data point.
        - y: 1-dimensional array of length N with real values.
        - reg: (float) regularization strength.

        Returns: A tuple containing:
        - loss as a single float
        - gradient with respect to self.theta; an array of the same shape as theta
        """
        theta,X,y,reg = args
        m,dim = X.shape
        J = 0

        ##########################################################################
        # Compute the loss function for regularized logistic regression          #
        # TODO: 1-2 lines of code expected                                       #
        ##########################################################################
        h = utils.sigmoid(X@theta)
        J = 1 / m * (-y.T@np.log(h) - (1 - y.T)@np.log(1 - h)) + reg / (2 * m) * (theta[1:].T@theta[1:])
        ###########################################################################
        #                           END OF YOUR CODE                              #
        ###########################################################################
        return J

    def grad_loss(self, *args):
        """
        Compute the gradient logistic loss function

        Inputs:
        - X: N x D array of data; each row is a data point.
        - y: 1-dimensional array of length N with real values.
        - reg: (float) regularization strength.

        Returns: A tuple containing:
        - loss as a single float
        - gradient with respect to self.theta; an array of the same shape as theta
        """
        theta,X,y,reg = args
        m,dim = X.shape
        grad = np.zeros((dim,))
        ##########################################################################
        # Compute the gradient of the loss function for regularized logistic
        # regression                                                             #
        # TODO: 2 lines of code expected                                          #
        ##########################################################################
        h = utils.sigmoid(X@theta)
        theta[0] = 0
        grad = 1 / m * (X.T@(h - y.T)) + reg / m * theta

        ###########################################################################
        #                           END OF YOUR CODE                              #
        ###########################################################################
        return grad

    def predict(self, X):
        """
        Use the trained weights of this linear classifier to predict probabilities for
        data points.

        Inputs:
        - X: N x D array of training data. Each row is a D-dimensional point.

        Returns:
        - y_pred: Predicted output for the data in X. y_pred is a 1-dimensional
        array of length N, and each element is a real number.
        """
        y_pred = np.zeros(X.shape[0])

        ###########################################################################
        # Compute the predicted outputs for X                                     #
        # TODO: 1 line of code expected                                           #
        #                                                                         #
        ###########################################################################
        y_pred = np.round(utils.sigmoid(X@self.theta))

        ###########################################################################
        #                           END OF YOUR CODE                              #
        ###########################################################################
        return y_pred

if __name__ == '__main__':
    A = np.array([0, 3, 1, 3, 0, 1, 1, 1]).reshape((4, 2))
    #prepend 1
    A = np.c_[np.ones(A.shape[0]), A]
    y = np.array([1, 1, 0, 0]).reshape(1, 4)
    theta = np.array([0, -2, 1]).reshape(3, 1)
    reg = 0.07
    #grad + hess
    h = utils.sigmoid(A @ theta)
    m, _ = A.shape
    test = h - y.T
    grad = 1 / m * A.T@(h - y.T) + reg / m * theta
    I = np.identity(grad.shape[0])
    HESS = 1 / m * (np.dot(A.T, A) * np.diag(h) * np.diag(1 - h)) + reg / m * I
    #Newton's law
    hessianInv = np.linalg.inv(HESS)
    theta1 = theta - np.dot(hessianInv, grad)
    theta2 = theta1 - np.dot(hessianInv, grad)
    print(f'After the first iteration is\n {theta1}')
    print(f'After the first iteration is\n {theta2}')


import numpy as np

def generate_circle_points(radius, initial_angle, final_angle, points=199):
    """
    This methods generates points in a circle shape at (0,0) with a specific radius and from a
    starting angle to a final angle.

    Args:
        radius: radius of the circle in microns
        initial_angle: initial angle of the drawing in degrees
        final_angle: final angle of the drawing in degrees
        points: amount of points to be generated (default 199)

    Returns:
        Set of points that form the circle
    """
    theta = np.linspace(    np.deg2rad(initial_angle),
                            np.deg2rad(final_angle),
                            points)

    return  radius * np.cos(theta) , radius * np.sin(theta)


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

# ## Van der Pohls euqation
# https://en.wikipedia.org/wiki/Van_der_Pol_oscillator

# In[1]:

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

saveFigure = False

def vdp(t, y, la):
    return [y[1], la*(1-y[0]**2)*y[1]-y[0]]

# In[2]:

t_span = (0, 40)

y0 = [2, 0]
la = 1

# Solve for Van der Pohls equation
sol_RK = solve_ivp(lambda t,y: vdp(t,y,la), t_span, y0, method='RK45' )
sol_BDF = solve_ivp(lambda t,y: vdp(t,y,la), t_span, y0, method='BDF' )

# Plot
fig, axs = plt.subplots(4, 1, figsize=(8, 8))
fig.tight_layout(pad=0, w_pad=0, h_pad=4)

ax =axs[0]
ax.plot(sol_RK.t,sol_RK.y[0].T )
ax.plot(sol_RK.t,sol_RK.y[1].T )
ax.autoscale(enable=True, axis='both', tight=True)
ax.set_ylabel(r'$y(t)$')
ax.set_xlabel(r'$t$')
ax.set_title('Solutions of $\lambda=$' + str(la) + ' with Runge-Kutta')

ax =axs[1]
ax.plot(sol_BDF.t,sol_BDF.y[0].T )
ax.plot(sol_BDF.t,sol_BDF.y[1].T )
ax.autoscale(enable=True, axis='both', tight=True)
ax.set_ylabel(r'$y(t)$')
ax.set_xlabel(r'$t$')
ax.set_title('Solutions of $\lambda=$' + str(la) + ' with BDF')

ax =axs[2]
ax.plot(sol_RK.t[0:-1], np.diff(sol_RK.t), )
ax.autoscale(enable=True, axis='both', tight=True)
ax.set_ylabel(r'$\Delta t$')
ax.set_xlabel(r'$t$')
ax.set_title('Step sizes with Runge-Kutta for $\lambda=$' + str(la))

ax =axs[3]
ax.plot(sol_BDF.t[0:-1], np.diff(sol_BDF.t), )
ax.autoscale(enable=True, axis='both', tight=True)
ax.set_ylabel(r'$\Delta t$')
ax.set_xlabel(r'$t$')
ax.set_title('Step sizes with BDF for $\lambda=$' + str(la))

if saveFigure:
    filename = 'vdp_la'+str(la)+'.pdf'
    fig.savefig(filename, format='pdf', dpi=1000, bbox_inches='tight')

# ## Satellites Orbit

# In[10]:

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

def satellite(t, y, k):
    sqt = (y[0]**2+y[1]**2)**(-1.5)
    return [y[2], y[3], -k*y[0]*sqt, -k*y[1]*sqt]
a = 0
b = 10

y0 = [a, b, 0.25, 0.25]
k = 10
t_span = (0, 100)
t_eval = np.linspace(t_span[0], t_span[1], 10000)

# Solve for orbiting equation
sol = solve_ivp(lambda t,y: satellite(t,y,k), t_span, y0,
                method='RK45', t_eval=t_eval)
fig, ax = plt.subplots(1, figsize=(6, 4.5))

ax.plot(sol.y[0].T,sol.y[1].T )
ax.plot(0, 0, 'ro', markersize=10)
ax.plot(a, b, 'k*', markersize=10)

ax.set_ylabel(r'$y$')
ax.set_xlabel(r'$x$')

# ## Stability Region

# ### Explicit

# In[4]:

# Grid
nx, ny = (100, 100)
x = np.linspace(-3, 3, nx)
y = np.linspace(-3, 3, ny)
X, Y = np.meshgrid(x, y)
Z = X + Y*1j

# Euler Forward
EulerF = abs(1+Z)

# Heun's method
Heuns = abs(0.5*((Z+1)**2+1))

# Runge-Kutta 4
RK4 = abs(1+Z+0.5*Z**2+Z**3/6+Z**4/24)

# Plot
fig, ax = plt.subplots(1, figsize=(6, 6))
ax.contour(X, Y, EulerF, levels = [1], colors='C0')
ax.contour(X, Y, Heuns, levels = [1], colors='C1')
ax.contour(X, Y, RK4, levels = [1], colors='C2')

ax.set_xlim([-3, 3])
ax.set_ylim([-3, 3])
ax.grid(True)
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')
ax.set_xlabel(r'$\Re(h\lambda)$')
ax.set_ylabel(r'$\Im(h\lambda)$')

proxy = [plt.Rectangle((0,0),1,1,fc ='C'+str(pc))  for pc in range(3)]
plt.legend(proxy, ['Euler Forward', 'Heuns Method', 'Classical Runge-Kutta 4'], loc='center right')

if saveFigure:
    filename = 'Explicit.pdf'
    fig.savefig(filename, format='pdf', dpi=1000, bbox_inches='tight')

# ### Implicit

# In[5]:

# Euler Backward
EulerBack = abs(1/(1-Z))

# Trapzoidal method
Trap = abs((2+Z)/(2-Z))

# Plot
fig, ax = plt.subplots(1, figsize=(6, 6))
ax.contour(X, Y, EulerBack, levels = [1], colors='C0')
ax.contour(X, Y, Trap, levels = [1], colors='C1')

ax.set_xlim([-3, 3])
ax.set_ylim([-3, 3])
ax.grid(True)
ax.axhline(y=0, color='k')
# ax.axvline(x=0, color='k')
ax.set_xlabel(r'$\Re(h\lambda)$')
ax.set_ylabel(r'$\Im(h\lambda)$')

proxy = [plt.Rectangle((0,0),1,1,fc ='C'+str(pc))  for pc in range(3)]
plt.legend(proxy, ['Euler Backward', 'Trapzoidal Method'], loc='upper right')

if saveFigure:
    filename = 'Implicit.pdf'
    fig.savefig(filename, format='pdf', dpi=1000, bbox_inches='tight')

# ### BDF methods

# In[6]:

n = 500
z = np.linspace(0, n-1, n)
z = z * 2 *np.pi/n

z = np.exp(z*1j)

# BDF methods
d = 1 - 1.0/z
r = 0
fig, ax = plt.subplots(1, figsize=(6, 6))
# Bdf 1 to 6
for i in range(6):
    r = r + (d**(i+1))/(i+1)
    ax.plot(r.real, r.imag, label='BDF-'+str(i+1))

ax.grid(True)
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')
ax.set_xlabel(r'$\Re(h\lambda)$')
ax.set_ylabel(r'$\Im(h\lambda)$')
plt.legend()

if saveFigure:
    filename = 'BDF.pdf'
    fig.savefig(filename, format='pdf', dpi=1000, bbox_inches='tight')

# Zoom in
ax.set_xlim([-6, 6])
ax.set_ylim([-6, 6])

if saveFigure:
    filename = 'BDF_zoom.pdf'
    fig.savefig(filename, format='pdf', dpi=1000, bbox_inches='tight')

# ## $\theta$-method

# In[7]:

#
nt = 10
ths = np.linspace(0, 1, nt)

# Plot
fig, ax = plt.subplots(1, figsize=(6, 6))
for i in range(nt):
    theta = abs((1-(1-ths[i])*Z)/(1+ths[i]*Z))
    ax.contour(X, Y, theta, levels = [1], colors='C'+str(i))

ax.set_xlim([-3, 3])
ax.set_ylim([-3, 3])
ax.grid(True)
ax.axhline(y=0, color='k')
# ax.axvline(x=0, color='k')
ax.set_xlabel(r'$\Re(h\lambda)$')
ax.set_ylabel(r'$\Im(h\lambda)$')

proxy = [plt.Rectangle((0,0),1,1,fc ='C'+str(pc))  for pc in range(3)]
plt.legend(proxy, ['theta'], loc='upper right')

# if saveFigure:
#     filename = 'Implicit.pdf'
#     fig.savefig(filename, format='pdf', dpi=1000, bbox_inches='tight')

# ## Reaction problem

# In[8]:

def reaction(t, y, s, q, w):
    return [s*(y[1]-y[0]*y[1]+y[0]-q*y[0]*y[0]),
           1.0/s*(-y[1]-y[0]*y[1]+y[2]),
           w*(y[0]-y[2])]

# In[9]:

# Time interval for the simulation
t0 = 0
t1 = 10
t_span = (t0, t1)
t_eval = np.linspace(t_span[0], t_span[1], 100)

# Initial conditions, e.g. number of predator and prey at time zero
alpha0 = 30
beta0 = 1
gamma0 = 30
y0 = [alpha0, beta0, gamma0]

# Set parameters
s = 77.27;
w = 0.161;
q = 8.375e-6;

# Solve for Van der Pohls equation
sol_BDF = solve_ivp(lambda t,y: reaction(t, y, s, q, w),
                t_span, y0, method='BDF')

sol_RK = solve_ivp(lambda t,y: reaction(t, y, s, q, w),
                t_span, y0, method='RK45')

fig, ax = plt.subplots(2, 1, figsize=(6, 6))
ax[0].plot(sol_BDF.t, sol_BDF.y.T)
ax[1].plot(sol_BDF.t[0:-1], np.diff(sol_BDF.t))

fig, ax = plt.subplots(2, 1, figsize=(6, 6))
ax[0].plot(sol_RK.t, sol_RK.y.T)
ax[1].plot(sol_RK.t[0:-1], np.diff(sol_RK.t))


"""Tests for integration lecture."""
from functools import partial

import chaospy as cp
import numpy as np
from integration_algorithms import monte_carlo_naive_one
from integration_algorithms import monte_carlo_naive_two_dimensions
from integration_algorithms import monte_carlo_quasi_two_dimensions
from integration_algorithms import quadrature_gauss_legendre_one
from integration_algorithms import quadrature_gauss_legendre_two
from integration_algorithms import quadrature_newton_simpson_one
from integration_algorithms import quadrature_newton_trapezoid_one
from scipy.stats import uniform

def test_1():
    """Return test 1."""
    approaches = []
    approaches += [quadrature_gauss_legendre_one, quadrature_newton_simpson_one]
    approaches += [monte_carlo_naive_one, quadrature_newton_trapezoid_one]
    for approach in approaches:
        np.testing.assert_almost_equal(approach(uniform.pdf, 0, 1, 11), 1.0)

def test_2():
    """Return test 2."""
    p_gc_legendre_two = partial(quadrature_gauss_legendre_two, a=0, b=1)

    approaches = []
    approaches += [monte_carlo_naive_two_dimensions, monte_carlo_quasi_two_dimensions]
    approaches += [p_gc_legendre_two]
    distribution = cp.J(cp.Uniform(0, 1), cp.Uniform(0, 1))
    for approach in approaches:
        np.testing.assert_almost_equal(approach(distribution.pdf), 1.0)


import numpy as np

def cart2pol(x, y):
    rho = np.sqrt(x ** 2 + y ** 2)
    phi = np.arctan2(y, x)

    return rho, phi

def pol2cart(rho, phi):
    x = rho * np.cos(phi)
    y = rho * np.sin(phi)

    return x, y


import numpy as np

# Activation
def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

def sigmoid_derivatif(x):
    return sigmoid(x) * (1.0 - sigmoid(x))

def tanh(x):
    return np.tanh(x)

def tanh_derivatif(x):
    return 1.0 - np.square(np.tanh(x))

def relu(x):
    return x * (x > 0)

def relu_derivatif(x):
    return 1 * (x > 0)

# # Loss
# def logloss(y, a):
#     return -(y * np.log(a) + (1.0 - y) * np.log(1.0 - a))

# def logloss_derivatif(y, a):
#     return (a - y) / (a * (1 - a))

map_activation = {
    'sigmoid': (sigmoid, sigmoid_derivatif),
    'tanh': (tanh, tanh_derivatif),
    'relu': (relu, relu_derivatif)
}


###############################################################################
#####                      CONJUGATE GRADIENT CLASS                       #####
###############################################################################

import numpy as np
import math
from scipy.sparse import csr_matrix as csr

class cg_serial ():
    def __init__(self, A , b):

        self.n = b.size
        self.b = b
        self.A = A

        self.converged = False
        self.numIter   = 0
        self.sol       = None
        self.residual  = -1
        return

    def solve(self,x0,tol=1.e-10):
        print('  > STARTING CONVERGENCE LOOP')
        # Initialisation
        k = 0
        xk = np.array(x0)
        rk = self.b - self.A @ xk
        dk = rk

        # Main loop
        while (np.linalg.norm(rk) > tol and k < 100.0*self.n):
            if (k % 1000 == 0) : print('    > Iteration k = ', k, ' , Residual = ', np.linalg.norm(rk))
            Adk = self.A @ dk

            # Alpha_k and x_{k+1}
            alpha = (dk.transpose() @ rk) / (dk.transpose() @ Adk)
            xk = xk + alpha * dk

            # r_{k+1}, we save r_k
            rkm1 = rk
            rk = rk - alpha * Adk

            # Beta_k and d_{k+1}
            beta = (rk.transpose() @ rk) / (rkm1.transpose() @ rkm1)
            dk = rk + beta * dk

            k = k+1
        self.sol = xk
        self.numIter = k
        self.residual  = np.linalg.norm(rk)
        self.converged = (self.residual <= tol)


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from scipy import signal
from matplotlib.ticker import LinearLocator, FormatStrFormatter

class GaussianStatistics():
    """
    """
    def MultivariateGaussian(self, mu, sd, N = 10, A = 1):
        """
            mu: location of peak value
                array
                    ex: 2D = [5., 5.]
            sd: spread of gaussian
                covariance cross-covariance matrix
                    ex: 2D = [[20., -1.],
                               [-1., 20]]
            N: spread radius
                any real number -> scalar field
            A: Amplitude of gaussian
                any real number -> scalar field
        """

        X = np.linspace(0, N, N)
        Y = np.linspace(0, N, N)
        X, Y = np.meshgrid(X, Y)

        pos = np.empty(X.shape + (2,))
        pos[:, :, 0] = X
        pos[:, :, 1] = Y

        n = mu.shape[0]
        sd_det = np.linalg.det(sd)
        sd_inv = np.linalg.inv(sd)
        N = np.sqrt((2*np.pi)**n * sd_det)

        fac = np.einsum('...k,kl,...l->...', pos-mu, sd_inv, pos-mu)

        return A * np.exp(-fac / 2)

    def DOG(self, iRad, eRad, iA, eA):
        """
        """
        eSd = np.array([[eRad**2, -1.],
                        [-1., eRad**2]])
        iSd = np.array([[(iRad)**2, -1.],
                        [-1., (iRad)**2]])
        mu  = np.array([iRad, iRad])
        self.iRad = iRad
        self.eRad = eRad
        self.pos = self.MultivariateGaussian(mu, eSd, N = 2*iRad, A = eA)
        self.neg = self.MultivariateGaussian(mu, iSd, N = 2*iRad, A = iA)
        return self.pos, self.neg

    def Visualize(self, Gaussian, _type = '2d'):
        """
        """
        X = np.arange(0, Gaussian.shape[0])
        Y = np.arange(0, Gaussian.shape[1])
        X, Y = np.meshgrid(X, Y)

        if _type == '3d':
            fig = plt.figure()
            ax = fig.gca(projection='3d')
            surf = ax.plot_surface(X, Y, Gaussian, cmap=cm.coolwarm,
                           linewidth=0, antialiased=False)
            # Customize the z axis.
            ax.set_zlim(-1.01, 1.01)
            ax.zaxis.set_major_locator(LinearLocator(10))
            ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
            fig.colorbar(surf, shrink=0.5, aspect=5)
            plt.show()
        else:
            plt.imshow(Gaussian)
            plt.show()

    def OrientationBar(self, N = 10,
                    mu=np.array([5., 5.]),
                    Sigma=np.array([[20., -19.8], [-19.8, 20]]),
                    theta=0, display=False):
        """
        """
        if (theta > 90.): theta = theta - 180.
        theta = theta*np.pi/180.

        # update Sigma based om theta
        Sigma = np.array([[ Sigma[0,0]*(np.pi*0.5-theta)**2 +1.,
                            Sigma[0,1]*theta*(np.pi*0.5-theta) + 1.],
                          [ Sigma[1,0]*theta*(np.pi*0.5-theta) + 1.,
                            Sigma[1,1]*(theta)**2 +1.]])

        _bar = self.MultivariateGaussian(mu, Sigma, N)
        if display: self.Visualize(_bar)
        return _bar


import mpmath as mp
import warnings

def zeta_function(s,a = 1,derivative = 0):
    """
    Returns the value for hurwitz-zeta function at s

    Parameters
    ----------
    s : int, float, complex
        denotes the value for which zeta function needs to be calculated
    a : int, float, complex, tuple
        denotes constant a in hurwitz zeta function
        default value is 1
        can be rational also like p/q, input must be a tuple (p,q) for this case, also p and q must be integers
    derivative : int
        denotes the n'th derivative
        default value is 0
    return : float, complex
        returns tha value of zeta function or its derivative

    """
    return mp.zeta(s,a,derivative)

def altzeta_function(s):
    """
    Returns tha value for dirichlet eta function (also alternating zeta function) at s.

    Parameters
    ----------
    s : int, float, complex
        denotes the value for which alternating zeta function needs to be calculated
    return : float, complex
        returns the value of alternating zeta function

    """
    return mp.altzeta(s)

def gamma_function(s):
    """
    Returns tha value for gamma function at s

    Parameters
    ----------
    s : int, float, complex
        denotes the value for which gamma function needs to be calculated
    return : float, complex
        returns the value for gamma function

    """
    return mp.gamma(s)

def bernoulli_num(n):
    """
    Returns n'th bernoulli number

    Parameters
    ----------
    n : int
        positive integer value
    return : float
        returns bernoulli number

    """
    return mp.bernoulli(n)


import numpy as np
import pylab as pl

from numpy import pi, sin, cos

# ***************************************************
#
#                       TEST 102
#
# ***************************************************
eps = 1.e-1
m = 2 ; n = 1

f = lambda y :  sin( n*pi * ( 4*y*(1-y) ) )
df = lambda y : (-4*pi*n*y + 4*pi*n*(-y + 1))*cos(4*pi*n*y*(-y + 1))

u = lambda x,y : eps*sin(n*pi*y)*cos(m*pi*x) + f(y)

t = np.linspace(0.,1.,200)
x,y = np.meshgrid(t,t)

# ...
# plot of f
# ...
pl.plot(t, f(t))
pl.title("plot of $f(y) = \sin( n\pi ( 4 y (1-y) ) )$ ")
pl.show()
# ...

# ...
# plot of df
# ...
pl.plot(t, df(t))
pl.title("plot of $df(y) = -4 \pi n y + 4 \pi n (-y + 1)) \cos(4 \pi n y (-y + 1) $ ")
pl.show()
# ...

# ...
# plot of u
# ...
pl.contourf(x,y,u(x,y))
pl.colorbar()
pl.title("plot of $u (x,y) = \epsilon \sin(n \pi y) \cos(m \pi x) + \sin( n \pi ( 4 y (1-y) ) )$ ")
pl.show()
# ...

# ...
# plot of u - limit
# ...
eps = 0.
pl.contourf(x,y,u(x,y))
pl.colorbar()
pl.title("plot of $u (x,y) =  \sin( n \pi ( 4 y (1-y) ) )$ ")
pl.show()
# ...

# ***************************************************

# ***************************************************
#
#                       TEST 101
#
# ***************************************************
eps = 1.e-1
m = 2 ; n = 1

u = lambda x,y : sin(n*pi*y) + eps * cos(m*pi*x) * sin(n*pi*y)

t = np.linspace(0.,1.,201)
x,y = np.meshgrid(t,t)

# ...
# plot of u
# ...
pl.contourf(x,y,u(x,y))
pl.colorbar()
pl.title("plot of $u (x,y) = \sin(n \pi y) ( 1 + \epsilon \cos(m \pi x) )$ ")
pl.show()
# ...

# ...
# plot of u limit
# ...
eps = 0.
pl.contourf(x,y,u(x,y))
pl.colorbar()
pl.title("plot of $u (x,y) = \sin(n \pi y) $ ")
pl.show()
# ...

# ***************************************************

# ***************************************************
#
#                       TEST 110
#
# ***************************************************
eps = 1.e-4
alpha = 2.0
m = 2
n = 1

u = lambda x,y : eps*sin(n*pi*y)*cos(m*pi*x) + sin(alpha*(y**2 - y)*cos(m*pi*x) + n*pi*y)

t = np.linspace(0.,1.,201)
x,y = np.meshgrid(t,t)

# ...
# plot of u
# ...
pl.contourf(x,y,u(x,y))
pl.colorbar()
pl.title("plot of $u (x,y) = \epsilon \sin(n \pi y) \cos(m \pi x) + \sin( \\alpha (y^2 - y) \cos(m \pi x) + n \pi y)$ ")
pl.show()
# ...

# ...
# plot of u - limit
# ...
eps = 0.
pl.contourf(x,y,u(x,y))
pl.colorbar()
pl.title("plot of $u (x,y) = \sin( \\alpha (y^2 - y) \cos(m \pi x) + n \pi y)$ ")
pl.show()
# ...

# ***************************************************


import math
from scipy import stats
#from scipy.stats.mstats import gmean
#from statistics import geometric_mean
'''testing different versions of calculating the geometric mean
for speed in application in gm_metric class'''

img = [1.0, 0.00001, 10000000000.]

def geomean_py(img):
    return math.exp(math.fsum(math.log(x) for x in img) / len(img))

def geomean_scipy1(img):
    return stats.gmean(img)

#def geomean_scipy2(img):
#    return gmean(img)

%time
geomean_py(img)
%time
geomean_scipy1(img)

# from statistics lib
#geometric_mean(img)


# -*- coding: utf-8 -*-
"""A variants of Discounted Cumulative Gain.
"""
from typing import Optional, Sequence
import numpy as np
from .types import Scalar

def _calc_numer(rels):
    """Calculate numerator of DCG

    params
    ------
    rels :
        np.ndarray, shape = (n_entities, )

    return
    ------
    numer :
        np.ndarray, The numerator of DCG
    """
    return np.array([2.0 ** rel - 1 for rel in rels])

def _calc_denom(n_entities):
    """Calculate denominator of DCG

    params
    ------
    n_entities :
        int, # of entities.

    return
    ------
    denom :
        np.ndarray, The denominator of DCG
    """
    return np.log2(np.arange(2, n_entities + 2))

def DCG(rels):
    """Calculate DCG based on given relevances of entities.

    params
    ------
    rels :
        array-like, shape = (n_entities, )
        i-th element represents the relevance score of i-th item.

    return
    ------
    dcg :
        float, The value of DCG.
    """
    numer = _calc_numer(rels)
    denom = _calc_denom(len(rels))
    return (numer / denom).sum()

def NDCG(rels: Sequence[Scalar], topk: Optional[int] = None) -> Scalar:
    """Compute NDCG based on given relevances of entities.

    Parameters:
        rels: array-like, shape (n, )
              i-th element represents the relevance score of i-th item.
        topk: int, k for NDCG@`k`. defaults to None.
              all of the relevance scores will be considerd if k is None.

    Retuls:
        ndcg: NDCG score
    """
    obs_rels: Sequence[Scalar]
    opt_rels: Sequence[Scalar]
    if topk is None:
        obs_rels = rels
        opt_rels = np.sort(rels)[::-1]
    else:
        obs_rels = rels[:topk]
        opt_rels = np.sort(rels)[::-1][:topk]
    dcg_obs = DCG(obs_rels)
    dcg_opt = DCG(opt_rels)
    return dcg_obs / dcg_opt


#!/usr/bin/env python3

"""
This script should show some of the power
of numpy arrays, and demonstrate their usage.

See the numpy documentation for more information
http://docs.scipy.org/doc/numpy/
start with the User Guide's quickstart
"""

import numpy as np

### The numpy array

# Numpy arrays are defined with an initial size, which cannot
# change without redefining the array. Furthermore, all elements
# of an array are of the same type, and must be a number. By
# default, this uses a float (think of a  float as a round rubber tube,
# j.k. its actually how computers store real numbers instead of integers)
# We can start by building an array with each element equal to 1
# it will have 2 dimensions, the first of which is 3 and the second
# of which is 4. Typically, this is thought of as 3 rows with 4 elements
# per row (4 columns), similiar to matrices.
a = np.ones((3,4))
print(a)

# Let us define an array b, and fill it with a multiplication table
b = np.zeros((3,4))
print(b)
# every array has a shape attribute, which stores its shape as tuple
print(b.shape)
for i in range(b.shape[0]):
    for j in range(b.shape[1]):
        # We can access the i row, j column entry using
        # either b[i][j], or b[i,j]
        b[i,j] = i*j
print(b)

# We can perform numerical operations on array, and they happen element-wise.
# So squaring b is equivalent to squaring each element of b
print('a')
print(a)
print('b')
print(b)
print('b**2')
print(b**2)
print('a + b')
print(a+b)
print('a*b')
print(a*b)
print('3*b - 2')
print(3*b - 2)

# Numpy has functions that will work element-wise on arrays
print('sqrt(b)')
print(np.sqrt(b))
print('arctan(b) in degrees')
print(np.rad2deg(np.arctan(b)))

# arithmetic does NOT work on arrays of different shapes if they both have
# the same dimension

# Turn a on its side
a = a.transpose()
print('a transpose')
print(a)

print('a + b')
try:
    print(a + b)
except Exception as e:
    print(e.args)

# But consider the following 0-dimension array
c = np.zeros((3,))
for i in range(c.shape[0]):
    c[i] = i+1
d = np.zeros((4,))
for i in range(d.shape[0]):
    d[i] = i+5

print('c')
print(c)
print('d')
print(d)

# Numpy will try to do a sensible 'broadcasting' to make arithemetic possible
print('b + c')
try:
    print(b + c)
except Exception as e:
    print(e.args)
print('b + d')
print(b + d)

# BEWARE THE NUMPY BROADCAST. it can be helpful, but also leads to weird bugs that are hard to track down

# We can also tranform numpy arrays to lists, and vice versa

# array to list. This transform preserves the [i][j] indexing
b_list = b.tolist()
print('list b')
print(b_list)

# list to array
c = np.array(b_list)
print('c array = np.array(b_list)')
print(c)

# BEWARE THE LIST/ARRAY CONFUSION.

# Note the difference between the array times a number and list times a number.
# It is easy to get this mixed up if you transform between these often.
print('b*3')
print(b*3)
print('np.array((list b)*3)')
print(np.array(b_list*3))

# You cannot add a non-list to a list, but you can for an array
print('b + 1')
print(b + 1)
print('(list b) + 1')
try:
    print(b_list + 1)
except Exception as e:
    print(e.args)


# https://www.codewars.com/kata/563e320cee5dddcf77000158/
# Return the average of the given array rounded down to its nearest integer.
# The array will never be empty.

import math

def get_average(marks):
    sum = 0
    for i in range(len(marks)):
        sum += marks[i]

    return math.floor(sum/len(marks))

def get_average(marks):
    sum = 0
    for num in marks:
        sum += num

    return math.floor(sum/len(marks))

def get_average(marks):
    sum = 0
    for num in marks:
        sum += num

    return math.floor(sum/len(marks))

def get_average(marks):
    return int(sum(marks) / len(marks))

def get_average(marks):
    return sum(marks) // len(marks)

import numpy
def get_average(marks):
    return int(numpy.mean(marks))

get_average =lambda x: sum(x)//len(x)

print(get_average([2, 2, 2, 2]), 2)
print(get_average([1, 5, 87, 45, 8, 8]), 25)
print(get_average([2, 5, 13, 20, 16, 16, 10]), 11)
print(get_average([1, 2, 15, 15, 17, 11, 12,
                   17, 17, 14, 13, 15, 6, 11, 8, 7]), 11)


from sympy import symbols, integrate, Rational, lambdify, sqrt, solve
import matplotlib.pyplot as plt
import numpy as np

def plot_fun( fun, name, col ):
	x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
	y_vals = fun( x_vals )
	plt.plot( x_vals, y_vals, label = name, color = col )

g_xlim = [ 1, 50 ]
g_ylim = [ -5, 15 ]

q = symbols( 'q', positive = True )

# The supply function for oil is given (in dollars) by
S = q**2 + 11*q

# and the demand function is given (in dollars) by:
D = 1054 - 17*q - q**2

# Find the point at which supply and demand are in equilibrium.
E = S - D

equilibrium_x = solve( E, q )[ 0 ]
equilibrium_y = S.subs( { q: equilibrium_x } )

# Find the consumers' surplus.

CS = D - equilibrium_y
consumer_surplus = integrate( CS, ( q, 0, equilibrium_x ) ).evalf()
round( consumer_surplus, 2 )

# Find the consumers' surplus.

SS = equilibrium_y - S
suplier_surplus = integrate( SS, ( q, 0, equilibrium_x ) ).evalf()
round( suplier_surplus, 2 )

# Graph
lam_s = lambdify( q, S, np )
plot_fun( lam_s, 'Supply', 'B' )

lam_d = lambdify( q, D, np )
plot_fun( lam_d, 'Demand', 'R' )

plt.legend()
plt.show()


from sympy.ntheory import factorint
import warnings
import numpy as np

def is_optimal_for_cuFFT(n: int, allowed_factors) -> bool:
    factorization = factorint(n)
    if len(factorization) == 0: # factorint(1) returns empyt dict
        return False
    factors = set(factorization.keys())
    return factors.issubset(set(allowed_factors))

def _closest_optimal(n: int, search_next_largest: bool, allowed_factors) -> int:
    while(not is_optimal_for_cuFFT(n, allowed_factors) and n>=1):
        if search_next_largest:
            n += 1
        else:
            n -= 1
    # edge case: decreasing search with start value smaller than allowed factor
    if n < min(allowed_factors):

        warnings.warn(f"{n}One provided dimension is smaller than smallest allowed factor and search direction is decreasing")
        return(min(allowed_factors))
    return n

def closest_optimal(n, search_next_largest: bool=True, allowed_factors=(2,3,5,7)):
    """ Finds closest optimal array dimensions for cuFFT

    Parameters
    ----------
    n : iterable of integers
        Input dimensions
    search_next_largest : bool
        if True (default) search closest optimal dimensions that are larger or equal to original
        otherwise look for smaller ones.
    allowed_factor: tuple of integers
        allowed factors in decomposition. Defaults to (2,3,5,7) which are the factors listed in
        the cuFFT documentation.

    Returns
    -------
    np.array of ints
        optimal dimensions for cuFFT

    See also
    --------
    https://docs.nvidia.com/cuda/cufft/index.html

    """
    n = np.asarray(n)
    scalar_input = False
    if n.ndim == 0:
        n = n[None]
        scalar_input = True
    ret = np.array([_closest_optimal(ni, search_next_largest, allowed_factors) for ni in n])
    if scalar_input:
        return ret[0]
    return ret


# -*- coding: utf-8 -*-
"""
Created on Wed May 23 14:39:09 2018

@author: gawe
"""

import numpy as _np
import numpy.linalg as _la
import matplotlib.pyplot as _plt

def cov(data):
    """
    Covariance matrix
    note: specifically for mean-centered data
    note: numpy's `cov` uses N-1 as normalization
    """
    return _np.dot(data.T, data) / data.shape[0]

def basic_pca(data, pc_count = None):
    """

   Principal component analysis using eigenvalues
    note: this mean-centers and auto-scales the data (in-place)
    """
    # data = data.copy()
    # data = data/_np.mean(data, 0)   # normalize the data

    data -= _np.mean(data, 0)
    data /= _np.std(data, 0)
    # C = _np.cov(data.T)   # numpy's covaraince calculation
    C = cov(data)       # simple one
    E, V = _la.eigh(C)
    key = _np.argsort(E)[::-1][:pc_count]
    E, V = E[key], V[:, key]
    # U = _np.dot(data, V)  # numpy's cov ....used to be _np.dot(V.T, data.T).T
    U = _np.dot(data, V)  # simple cov .... two PC's?
    return U, E, V

def test(data=None):
    if data is None:
        data = test_data()
    # end if

    """ visualize """
    trans = basic_pca(data, 3)[0]
    fig, (ax1, ax2) = _plt.subplots(1, 2)

    ax1.scatter(data[:50, 0], data[:50, 1], c = 'r')
    ax2.scatter(trans[:50, 0], trans[:50, 1], c = 'r')

    ax1.scatter(data[50:, 0], data[50:, 1], c = 'b')
    ax2.scatter(trans[50:, 0], trans[50:, 1], c = 'b')
    _plt.show()
# end def test()

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

def PCA(data, dims_rescaled_data=2):
    """
    returns: data transformed in 2 dims/columns + regenerated original data
    pass in: data as 2D NumPy array
    """
    m, n = data.shape
    # mean center the data
    data -= data.mean(axis=0)
    # calculate the covariance matrix
    R = _np.cov(data, rowvar=False)
    # calculate eigenvectors & eigenvalues of the covariance matrix
    # use 'eigh' rather than 'eig' since R is symmetric,
    # the performance gain is substantial
    evals, evecs = _la.eigh(R)
    # sort eigenvalue in decreasing order
    idx = _np.argsort(evals)[::-1]
    evecs = evecs[:,idx]
    # sort eigenvectors according to same index
    evals = evals[idx]
    # select the first n eigenvectors (n is desired dimension
    # of rescaled data array, or dims_rescaled_data)
    evecs = evecs[:, :dims_rescaled_data]
    # carry out the transformation on the data using eigenvectors
    # and return the re-scaled data, eigenvalues, and eigenvectors
    return _np.dot(evecs.T, data.T).T, evals, evecs

def test_PCA(data=None, dims_rescaled_data=2):
    '''
    test by attempting to recover original data array from
    the eigenvectors of its covariance matrix & comparing that
    'recovered' array with the original data
    '''
    if data is None:
        data = test_data()
    # end if

    m, n = data.shape
    _ , _ , eigenvectors = PCA(data.copy(), dims_rescaled_data=dims_rescaled_data)
    # data_recovered = _np.dot(eigenvectors, m).T
    # data_recovered += data_recovered.mean(axis=0)
    # data_recovered = _np.dot(eigenvectors, _np.ones((n,m))).T
    data_recovered = _np.dot(eigenvectors.T, data.T).T
    # data_recovered += data.mean(axis=0)
    # data_recovered += data_recovered.mean(axis=0)

    _plt.figure()
    _plt.plot(data, '-')
    _plt.plot(data_recovered, '.')
    # assert _np.allclose(data, data_recovered)

    plot_pca(data)
# end def

def plot_pca(data, pcindices = [0,1]):
    clr1 =  '#2026B2'
    fig = _plt.figure()
    ax1 = fig.add_subplot(111)
    data_resc, eigenval, eivenvec = PCA(data.copy())

    # _plt.figure()
    # ax1.plot(data_resc[:, 0], data_resc[:, 1], '.', mfc=clr1, mec=clr1)

    _plt.figure()
    _ax1 = _plt.subplot(3, 1, 1)
    _ax1.plot(data, '-', data_resc, '.')

    _ax2 = _plt.subplot(3, 1, 2)
    _ax2.plot(1+_np.arange(0, len(eigenval)), eigenval, 's-')
    _ax2.set_ylabel('eigval')

    _ax3 = _plt.subplot(3, 1, 3)
    _ax3.plot(data_resc[:,pcindices[0]], data_resc[:,pcindices[1]], '.', mfc=clr1, mec=clr1)
    _ax3.set_xlabel('PC%i'%(pcindices[0],))
    _ax3.set_ylabel('PC%i'%(pcindices[1],))
    _plt.show()

def test_data():
    """ test data """
    data = _np.array([_np.random.randn(8) for k in range(150)])
    data[:50, 2:4] += 5
    data[50:, 2:5] += 5
    return data
# end def

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

if __name__=="__main__":

    test()
    test_PCA()
# end if


'''
In this assignment you will implement one or more algorithms for the all-pairs shortest-path problem. Here are data files describing three graphs:

The first line indicates the number of vertices and edges, respectively. Each subsequent line describes an edge (the first two numbers are its tail and head, respectively)
and its length (the third number). NOTE: some of the edge lengths are negative. NOTE: These graphs may or may not have negative-cost cycles.

Your task is to compute the "shortest shortest path". Precisely, you must first identify which, if any, of the three graphs have no negative cycles. For each such graph,
you should compute all-pairs shortest paths and remember the smallest one (i.e., compute \min_{u,v \in V} d(u,v)min u,v ∈ V d(u,v), where d(u,v)d(u,v) denotes the shortest-path distance from uu to vv).

If each of the three graphs has a negative-cost cycle, then enter "NULL" in the box below. If exactly one graph has no negative-cost cycles, then enter the length of its shortest shortest
path in the box below. If two or more of the graphs have no negative-cost cycles, then enter the smallest of the lengths of their shortest shortest paths in the box below.

OPTIONAL: You can use whatever algorithm you like to solve this question. If you have extra time, try comparing the performance of different all-pairs shortest-path algorithms!

OPTIONAL: Here is a bigger data set to play with.
'''

import os, math
from collections import defaultdict
from heapq import heappop, heappush

import numpy as np
from tqdm import tqdm

data_dir = os.getcwd() + '\\data\\illuminated\\shortest-paths\\'

submission_file_paths = [data_dir + sub for sub in ['g1.txt', 'g2.txt', 'g3.txt']]
big_submission_file_path = data_dir + 'large.txt'

test_file_path = data_dir + 'problem18.8test1.txt'

def bellman_ford(graph, graph_inverted):
    """
    Implementation of the Bellman-Ford algorithm to find the single-source shortest path for a graph possibly containing
    negative edges.
    """

    print('creating modified graph by adding artificial start vertex s (id=0) with zero-edge to each vertex')

    s = 0
    graph_ = defaultdict(set, graph)
    graph_inverted_ = defaultdict(set, graph_inverted)

    for vertex in graph.keys():
        graph_[s].add(vertex)
        graph_inverted_[vertex].add((0, s))

    n = len(graph_)
    A = np.full(n, np.inf)
    A[s] = 0

    # we don't need to compute the full DP matrix, only the last 2 columns
    arrays_are_equal = False
    for _ in tqdm(range(1, n + 1)):
        A_next = np.full(n, np.inf)
        for v in graph.keys():
            case_1 = A[v]

            if v in graph_inverted:
                # v has incoming edges from vertices w: compute path to v with at most i-1 edges from all ws
                case_2 = min(A[w] + c for (c, w) in graph_inverted[v])
            else:
                case_2 = math.inf

            A_next[v] = min(case_1, case_2)

        arrays_are_equal = np.array_equal(A, A_next)

        if arrays_are_equal:
            print('early stopping because no change in previous iteration')
            return A[1:]

        A = A_next

    if not arrays_are_equal:
        print('negative cycle detected in additional iteration')
        return None

    print('no negative cycle detected')
    return A[1:]

def dijkstra(graph, s, t):
    """
    Heap-based implementation of Dijkstra's algorithms for SSSP for a given source vertex s and target vertex t
    :param graph: graph as dictionary mapping the edges for each vertex as follows:
                    source_vertex -> {(cost, target_vertex}
    :param s: start vertex
    :param t: target vertex
    :return: the cost and path of the minimal path from s to t if there is one, else infinity
    """
    q, seen, mins = [(0, s, ())], set(), {s: 0}
    while q:
        (cost, v1, path) = heappop(q)
        if v1 not in seen:
            seen.add(v1)
            path = (v1, path)
            if v1 == t:
                return cost, path

            for c, v2 in graph.get(v1, ()):
                if v2 in seen:
                    continue
                prev = mins.get(v2, None)
                next = cost + c
                if prev is None or next < prev:
                    mins[v2] = next
                    heappush(q, (next, v2, path))

    return float("inf")

def johnson(filename):

    print(f'processing {filename}')

    with open(filename) as f:
        lines = f.readlines()

    n_vertices, n_edges = map(int, lines[0].split())

    print(f'number of vertices: {n_vertices}')
    print(f'number of edges: {n_edges}')

    # create graph as dict: source -> (cost, target)
    graph = defaultdict(set)
    graph_inverted = defaultdict(set)  # precompute incoming edges for performance speedup

    for line in lines[1:]:
        u, v, c = map(int, line.split())
        graph[u].add((c, v))
        graph_inverted[v].add((c, u))

    assert len(graph.keys()) == n_vertices, 'length of vertices does not match'
    assert len(list(edge for edges in graph.values() for edge in edges)) == n_edges, 'length of edges does not match'

    print('running Bellman-Ford once to get single-source shortest path for a graph with possibly negative edges')

    A = bellman_ford(graph, graph_inverted)

    if A is None:
        # negative cost cycle detected
        return math.inf

    return min(A)

for s in submission_file_paths:
    print(f'{s}: {johnson(s)}')


import matplotlib.pyplot as plt
import numpy as np
from scipy import signal
from scipy.io import wavfile
from scipy.fftpack import rfft, irfft, rfftfreq
from ipywidgets import widgets
from IPython.display import display

#
# Fourier Series Code
#

#  funtions for fourier approximation
def f1(x,xMin,xMax):
    ''' f1(x) = x
    '''
    return x

def f2(x,xMin,xMax):
    ''' Square wave
               ~ -1, -1 < x < 0
        f2(x)= |
               ~  1,  0 < x < 1
    '''
    return signal.square(2*x)

def f3(x,xMin,xMax):
    ''' Cubic
    '''

    return 5*x*(x+1.)*(x-1.)

def example_4b(x,xMin,xMax):
    T = np.abs(xMax-xMin)
    f = np.zeros(len(x))
    for i in range(len(x)):
        xc = x[i]-np.rint(x[i]/T)*T
        if xc < -0.5*np.pi or xc > 0.5*np.pi:
            f[i] = -1.
        else:
            f[i] = 1.
    return f

def example_4c(x,xMin,xMax):
    T = np.abs(xMax-xMin)
    f = np.zeros(len(x))
    for i in range(len(x)):
        f[i] = x[i]-np.rint(x[i]/T)*T
    return f

def example_5a_sine(x,xMin,xMax):
    T = np.abs(xMax-xMin)
    f = np.zeros(len(x))
    for i in range(len(x)):
        xc = x[i]-np.rint(x[i]/T)*T
        f[i] = xc
    return f

def example_5a_cosine(x,xMin,xMax):
    T = np.abs(xMax-xMin)
    f = np.zeros(len(x))
    for i in range(len(x)):
        xc = x[i]-np.rint(x[i]/T)*T
        if xc < 0.:
            f[i] = -1.*xc
        else:
            f[i] = 1.*xc
    return f

def example_5b_sine(x,xMin,xMax):
    T = np.abs(xMax-xMin)
    f = np.zeros(len(x))
    for i in range(len(x)):
        xc = x[i]-np.rint(x[i]/T)*T
        if xc < 0.:
            f[i] = -1.
        else:
            f[i] = 1.
    return f

def example_5b_cosine(x,xMin,xMax):
    T = np.abs(xMax-xMin)
    f = np.zeros(len(x))
    for i in range(len(x)):
        xc = x[i]-np.rint(x[i]/T)*T
        if xc < 1. and xc > -1.:
            f[i] = 1.
        else:
            f[i] = 0.
    return f

def produceFourierSeries(x, nMax, xMin, xMax, f):
    """ Produces Fourier Series approximation of a function

        ----------

        Parameters

        ----------

        x: array_like
            sample values

        nMax: int
            number of terms

        xMin: float
            lower bound on window/approximation

        xMax: float
            upper bound on window/approximation

        f: function
            function to be approximated

    """
    T = xMax - xMin

    # need new range over the actual range of the approximation (not the plotting range)
    # so integration is correct
    xFuncPeriod = np.arange(xMin, xMax, 0.001)

    # set initial state of series to be the a0 constant
    series = np.zeros(len(x))
    series += (1./T)*np.trapz(f(xFuncPeriod,xMin,xMax), x=xFuncPeriod)

    prev = None

    for i in range(nMax):
        n = i+1 # set n term number based on loop counter
        an = (2./T)*np.trapz(f(xFuncPeriod,xMin,xMax)*np.cos(2*n*np.pi*xFuncPeriod*(1/T)), x=xFuncPeriod)
        bn = (2./T)*np.trapz(f(xFuncPeriod,xMin,xMax)*np.sin(2*n*np.pi*xFuncPeriod*(1/T)), x=xFuncPeriod)

        prev = np.add(an * np.cos(2*n*np.pi*x*(1/T)), bn * np.sin(2*n*np.pi*x*(1/T)))
        series = np.add(series, prev)

    return series, prev

def fourierMain(function, nMax, showPrevTerm):
    """
        Main function for calling produceFourierSeries and plotting

        ----------

        Parameters

        ----------

        function: string
            function key

        nMax: int
            number of terms

        showPrevTerm: bool
            true if most recent term should be displayed. False otherwise
    """

    # window upper and lower
#     xMin = -np.pi
#     xMax = np.pi

    functions = {'Linear':[f1, -np.pi, np.pi],'Square Wave':[f2, -np.pi/2, np.pi/2],'Cubic':[f3, -np.pi, np.pi],\
                '4B':[example_4b,-np.pi,np.pi],'4C':[example_4c,-np.pi,np.pi],\
                '5A_Sine':[example_5a_sine,-1.,1.],'5A_Cosine':[example_5a_cosine,-1.,1.],
                '5B_Sine':[example_5b_sine,-1.,1.],'5B_Cosine':[example_5b_cosine,-2.,2.]}

    f, xMin, xMax = functions[function]
    x = np.arange(xMin, xMax, 0.001)
    fx = f(x,xMin,xMax)
    fMin = np.min(fx)
    fMax = np.max(fx)

    _, ax = plt.subplots(figsize=(16,10))

    seriesApprox, prev = produceFourierSeries(x, nMax, xMin, xMax, f)

    if nMax > 0:
        label_tmp = 'Partial Sums, N={}'
    else:
        label_tmp = 'a0'

    ax.plot(x, seriesApprox, label=label_tmp.format(nMax), color='C0')
    # plot prev term if necessary. Don't plot if no prev term. (Shouldnt be possible with slider lim anyway)
    if (nMax > 0) and showPrevTerm: ax.plot(x, prev, label='Latest Term', color='g')
    ax.plot(x, fx, label='Function over Fundamental Range', color='C1')

    xbuffer = np.abs(xMax-xMin)/5.
    ybuffer = np.abs(fMax-fMin)/5.

    ax.set_xlabel('x')
    ax.set_ylabel('f(x)')
    ax.set_xlim(xMin-xbuffer,xMax+xbuffer)
    ax.set_ylim(fMin-ybuffer,fMax+ybuffer)
    ax.axhline(0,ls='-',c='k',lw=0.5)
    ax.axvline(0,ls='-',c='k',lw=0.5)
    ax.legend()
    plt.show()

def FourierSeries():
    """
        Main function called by notebook
    """
    terms_sldr = widgets.IntSlider(value=0, description='Terms', min=0, max=100, display=False, continuous_update=False)
    func_drp = widgets.Dropdown(options=['Linear','Square Wave','Cubic','4B','4C','5A_Sine','5A_Cosine'\
                                        ,'5B_Sine','5B_Cosine'],description='Function')
    prevTerm_check = widgets.Checkbox(value=False, description='Show most recent term')
    return widgets.VBox([widgets.HBox([terms_sldr, func_drp, prevTerm_check]), widgets.interactive_output(fourierMain, {'function':func_drp, 'nMax':terms_sldr, 'showPrevTerm':prevTerm_check})])

#
# Signal Processing Code
#

# some useful parameters

# min and max frequency for genwaves
minf = 1
maxf = 5

# file names and descriptor in dropdown
files = {'Guitar A string':'anote.wav', 'Bell C6':'c3Bell.wav', 'Synth':'generatedAudio.wav'}
path = "sound/"
# both these values depend on the exact .wav file you have.
# Must be right for the frequency readout to be correct but will still give general shape
sampFreq = 44100. # sample frequency, MUST be a float
bitDepth = 16

def genWaves(noWaves, seed):
    """ produces amplitude and angular velocity of waves and places them in an array noWaves long with [a, w, s]
        where:

        ----------

        Parameters

        ----------

        noWaves: int
            number of waves to produce

        seed: int
            seed of random number generator

        ----------

        Returns

        ----------

        array_like
            noWaves by 3 with, a = amplitude, w = angular velocity s = horizontal shift

    """
    np.random.seed(seed)

    waves = []
    for _ in range(noWaves):
        f = np.random.uniform(minf,maxf)
        a = np.random.uniform()/(f) # crude downward bias on the amplitude of high frequencies
        s = np.random.uniform(0, 2*np.pi)

        waves.append([a, f, s])
    return waves

def plotWaves(waves):
    """ Main plotting function for RandomWave. Also sums waves to produce signal

        ----------

        Parameters

        ----------

        waves: array_like
            noWavesx3 array that contains amplitude, angular velocity and horizontal shift of each wave
    """
    _, (rawAx, compAx, ampAx) = plt.subplots(3,figsize=(16,10))
    x = np.arange(0,2*np.pi,0.01)
    out = 0
    for wave in waves:
        a, f, s = wave
        w = 2*np.pi*f
        out += a*np.sin(w*x-s)

        compAx.plot(x, a*np.sin(w*x-s), ls="--",linewidth=0.5)

    rawAx.plot(x, out, linewidth=2)
    freqAmp = np.array(waves).T
    order = np.argsort(freqAmp[1])
    freq = freqAmp[1][order]
    amp = freqAmp[0][order]
    ampAx.bar(freq, amp, width=0.1)

    compAx.set_title("Broken into Components")
    rawAx.set_title("Raw Signal")
    ampAx.set_xlim(minf,maxf)
    ampAx.set_xlabel("Frequency")
    ampAx.set_ylabel("Amplitude")
    ampAx.set_title("Frequency vs. Amplitude of Components")

    compAx.xaxis.set_ticklabels([])
    compAx.yaxis.set_ticklabels([]) # Removing all trace of axis labels because it looks prettier
    rawAx.xaxis.set_ticklabels([])
    rawAx.yaxis.set_ticklabels([])

    compAx.yaxis.set_ticks([])
    rawAx.xaxis.set_ticks([])
    compAx.xaxis.set_ticks([])
    rawAx.yaxis.set_ticks([])
    plt.show()

def runWaves(noWaves, seed, filterRange=[minf, maxf]):
    """ Filters waves from genWaves
        ----------

        Parameters

        ----------

        noWaves: int
            number of waves to produce

        seed: int
            seed for random number generator

        filterRange: array_like
            pair of min and max filter values
    """
    filterMin, filterMax = filterRange
    waves = genWaves(noWaves, seed)
    # filter frequency outside allowed range
    filteredWaves = [wave for wave in waves if((wave[1] < filterMin) or wave[1] > filterMax)]
    if len(filteredWaves) > 1: plotWaves(filteredWaves)

def RandomWave():
    """ Main function called by notebook
    """
    noWaves_sldr = widgets.IntSlider(value=10, min=2, max=20, step=1, description='No. Waves', continuous_update=False)
    seed_Text = widgets.IntText(123, description='Seed')
    filter_sldr = widgets.FloatRangeSlider(value=[minf, minf],
                                           min=minf,
                                           max=maxf,
                                           description="Filter Range",
                                           continuous_update=False)
    return widgets.VBox([
        widgets.HBox([noWaves_sldr,filter_sldr,seed_Text]),
        widgets.interactive_output(runWaves, {'noWaves':noWaves_sldr, 'seed':seed_Text, 'filterRange':filter_sldr})])

##########

# Fourier Analysis

##########

def readWav(fileName):
    """
        Reads data from .wav

        ----------

        Parameters

        ----------

        fileName: str
            name of file or path

        ----------

        Returns

        ----------

        Raw audio data

    """

    _, data = wavfile.read(path+fileName) # load the data

    audioData = data.T
    # check if wav is stereo
    if len(data.shape)>1:
        audioData = audioData[0]
    return audioData

def processSignal(audioData):
    """
        Performs actual transform and returns:
        raw wav data
        transformed signal
        frequencies of transformed data

        ----------

        Parameters

        ----------

        audioData: array_like
            raw audio data

        ----------

        Returns

        ----------

        audioData: array_like
            Raw audio data

        tranformed: array_like
            frequency domain data

        freqs: array_like
            frequencies

    """
    tranformed = (rfft(audioData)) # perform transform

    freqs = rfftfreq(len(tranformed), d=1/sampFreq)

    return [audioData, tranformed, freqs]

def FourierAnalysis():
    """
        Called in notebook. Returns fft'd and raw data from bell and guitar .wavs.
        Calls the processSignal.

        for each file returns: [audio, tranformed, freqs]

    """
    # this is where I got the files from:
    #       guitar: https://freesound.org/people/casualdave/sounds/44729/
    #       bell  : https://freewavesamples.com/steel-bell-c6
    #       I made synth

    out = []

    for _, value in files.items():
        out.append(processSignal(readWav(value)))

    return out

def MusicNote(audioData, freqDom, freqs):
    """
        Produces UI componants for user. Drives plotting of PlotFourierAnalysis

        ----------

        Parameters

        ----------

        audioData: array_like
            raw audio data

        freqDom: array_like
            transformed frequency domain data

        freqs: array_like
            frequencies
    """
    xlim_sldr = widgets.IntRangeSlider(value=[0, 22.5e3], min=0, max=22.5e3, step=1000, continuous_update=False, description='Ax. lim')

    return widgets.VBox([widgets.interactive_output(PlotSignal, {
            'signal':widgets.fixed(audioData),
            'amps':widgets.fixed(freqDom),
            'freqs':widgets.fixed(freqs),
            'FALim':xlim_sldr}),
        widgets.HBox([xlim_sldr])])

def FilterFreqs(audioData, freqDom, freqs):
    """
        Produces UI componants for user. Drives plotting of filtered signal (FilterFreqs)

        ----------

        Parameters

        ----------

        audioData: array_like
            raw audio data

        freqDom: array_like
            transformed frequnecy domain data

        freqs: array_like
        frequencies
    """
    filter_sldr = widgets.IntRangeSlider(value=[0,0], min=0, max=23e3, step=1000, continuous_update=False, description='Freq Band')
    xlim_sldr = widgets.IntRangeSlider(value=[0, 22.5e3], min=0, max=22.5e3, step=1000, continuous_update=False, description='Ax. lim')
    export_btn = widgets.ToggleButton(value=False, description='Export to .wav')

    display(widgets.VBox([widgets.interactive_output(FilterBand, {
            'audioData':widgets.fixed(audioData),
            'freqDom':widgets.fixed(freqDom),
            'freqs':widgets.fixed(freqs),
            'filtFreq':filter_sldr,
            'FALim':xlim_sldr,
            'export':export_btn
            }),
        widgets.HBox([xlim_sldr, filter_sldr, export_btn])]))

def FilterBand(audioData, freqDom, freqs, filtFreq, FALim, export):
    """
        Sets amplitudes in transform to 0 that are outside frequency range. Performs inverse tranform
        Calls PlotSignal

        ----------

        Parameters

        ----------

        audioData: array_like
            raw audio data

        processed: array_like
            fourier transformed data

        freqs: array_like
            frequencies for filtering and inversion

        filtFreq: array_like
            pair of frequencies representing filter band

        FALim: array_like
            pair of values representing x axis limits for analysis plot
    """
    # list comprehension alternative. Slower.
    # filteredTrans = [0 if (freqs[i] >= filtFreq[0]) and (freqs[i] <= filtFreq[1]) else freqDom[i] for i in range(len(freqDom))]

    # to store filtered transformed data
    # filteredTrans = np.zeros(len(freqDom))
    filteredTrans = freqDom.copy()
    for i in range(len(freqDom)):
        if ((freqs[i] >= filtFreq[0]) and (freqs[i] <= filtFreq[1])):
            filteredTrans[i] = 0

        if freqs[i] > filtFreq[1]: break

    # invert to filtered signal
    filteredSignal = irfft(filteredTrans)
    # slice out negatives
    filteredSignal = (filteredSignal[:int(len(filteredSignal)/2)])

    if export:
        exportWav(filteredSignal)

    PlotSignal(filteredSignal, filteredTrans, freqs, FALim)

def PlotSignal(signal, amps, freqs, FALim):
    """
        Inverts and plots filterd signal.
        2 figures, freq and time domains.
        Called by FilterBand and MusicNote

        ----------

        Parameters

        ----------

        signal: array_like
            audio data to be plotted on top plot ()

        amps: array_like
            amplitude data for fourier analysis graph

        freqs: array_like
            freqencies for analysis graph

        FALim: array_like
            Fourier analysis plot x limits
    """

    _, (ax1, ax2) = plt.subplots(2, figsize=(16,8))

    ax1.plot(signal, linewidth=0.1)
    ax1.set_title('Signal')
    ax1.set_xlim(0)
    ax1.set_ylim(-35e3, 35e3)

    # commenting scale on x because samples arent super useful. Can convert to seconds.
    ax1.xaxis.set_ticklabels([])
    ax1.xaxis.set_ticks([])

    ax2.set_title('Fourier Analysis of Signal')
    ax2.plot(freqs, np.abs(amps), 'r', linewidth=0.5)
    ax2.set_yscale('log')
    ax2.set_xlim(*FALim)
    ax2.set_xlabel('Frequency (Hz)')
    ax2.set_ylabel('Amplitude')
    ax2.set_ylim(1e-3, 1e9)

    plt.show()

def exportWav(audioData):
    """ Function for exporting signal to wav

        ----------

        Parameters

        ----------

        audioData: array_like
            discrete audio signal
    """
    audioData = np.asarray(audioData, dtype=np.int16)

    wavfile.write(path+'exportedAudio.wav',int(sampFreq), audioData)

##########

# Even odd

##########

def oddSquare(x):
    ''' Square wave
               ~ -1, -1 < x < 0
        f2(x)= |
               ~  1,  0 < x < 1
    '''
    return signal.square(2*x)

def evenSquare(x):
    ''' Square wave
               ~ -1, -1 < x < 0
        f2(x)= |
               ~  1,  0 < x < 1
    '''
    return signal.square(2*(x + np.pi/4))

def cos2(x):
    return np.cos(2*x)

def sin2(x):
    return np.sin(2*x)

def quad(x):
    return x**2

def EvenOdd():
    funcs = {"cos(x)":np.cos, "cos(2x)":cos2,"cos(2x)":cos2,"sin(x)":np.sin, "x^2":quad, "Odd Square":oddSquare, "Even Square":evenSquare}
    f1_drop = widgets.Dropdown(options=funcs)
    f2_drop = widgets.Dropdown(options=funcs)
    sym_check = widgets.Checkbox(value=False, description="Check Symmetry")
    area_check = widgets.Checkbox(value=False, description="Integration Region")

    display(
        widgets.VBox([
            widgets.HBox([
                f1_drop,
                f2_drop,
                sym_check,
                area_check
                ]),
            widgets.interactive_output(symmetryCheck, {'f1':f1_drop, 'f2':f2_drop, "showSym":sym_check, "showArea":area_check})
        ])
    )

def symmetryCheck(f1, f2, showSym, showArea):

    a_slider = widgets.FloatSlider(value=1, min=0, max=np.pi, step=0.1, continuous_update=False)

    if not showSym:
        displayEvenOdd(f1, f2, showArea)
    else:
        display(
            widgets.VBox([
                a_slider,
                widgets.interactive_output(displayEvenOdd, {"f1":widgets.fixed(f1), "f2":widgets.fixed(f2), "showArea":widgets.fixed(showArea), "a":a_slider})
            ])
        )

def displayEvenOdd(f1, f2, showArea, a=None):

    x = np.linspace(-np.pi, np.pi,200)

    fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(16, 8))
    fig.tight_layout()

    ax1.plot(x, f1(x), color='C0')
    ax1.set_title('Function 1')
    ax2.plot(x, f2(x), color='C0')
    ax2.set_title('Function 2')
    result = np.multiply(f1(x), f2(x))
    ax3.plot(x, result, color='C3')
    ax3.set_title('Function 1 * Function 2')

    if a is not None:
        ax1.plot(a, f1(a), marker=".", markersize="15", color="C4", label="f({})".format(a))
        ax1.plot(-1*a, f1(a), marker=".", markersize="15", color="C6", label="f(-{})".format(a))

        ax2.plot(a, f2(a), marker=".", markersize="15", color="C4", label="f({})".format(a))
        ax2.plot(-1*a, f2(a), marker=".", markersize="15", color="C6", label="f(-{})".format(a))

        ax3.plot(a, f1(a)*f2(a), marker=".", markersize="15", color="C4", label="f({})".format(a))
        ax3.plot(-1*a, f1(a)*f2(a), marker=".", markersize="15", color="C6", label="f(-{})".format(a))

        ax1.legend()
        ax2.legend()

    if showArea:

        ax1.fill_between(x[:round(len(x)/2)], f1(x[:round(len(x)/2)]), np.zeros(round(len(x)/2)), color='C0', alpha=0.1)
        ax1.fill_between(x[round(len(x)/2):], f1(x[round(len(x)/2):]), np.zeros(round(len(x)/2)), color='C3', alpha=0.1)

        ax2.fill_between(x[:round(len(x)/2)], f2(x[:round(len(x)/2)]), np.zeros(round(len(x)/2)), color='C0', alpha=0.1)
        ax2.fill_between(x[round(len(x)/2):], f2(x[round(len(x)/2):]), np.zeros(round(len(x)/2)), color='C3', alpha=0.1)

        ax3.fill_between(x[:round(len(x)/2)], result[:round(len(x)/2)], np.zeros(round(len(x)/2)), color='C0', alpha=0.1)
        ax3.fill_between(x[round(len(x)/2):], result[round(len(x)/2):], np.zeros(round(len(x)/2)), color='C3', alpha=0.1)

    ax1.axvline(0, color='k', linewidth=0.5)
    ax2.axvline(0, color='k', linewidth=0.5)
    ax3.axvline(0, color='k', linewidth=0.5)

    ax1.axhline(0, color='k', linewidth=0.5)
    ax2.axhline(0, color='k', linewidth=0.5)
    ax3.axhline(0, color='k', linewidth=0.5)

    ax1.set_xlim(-np.pi, np.pi)
    ax2.set_xlim(-np.pi, np.pi)
    ax3.set_xlim(-np.pi, np.pi)


import numpy as np
import matplotlib.pyplot as plt

def norm_histogram(hist):
    """
    takes a histogram of counts and creates a histogram of probabilities

    :param hist: list
    :return: list
    """

    j = 0;
    hist_sum = 0;

    hist_new = [];

    while j < len(hist):
        hist_sum =  hist_sum + hist[j];
        j += 1;

    for i in hist:
        p = i / hist_sum
        hist_new.append(p);

    return(hist_new)

    pass

def compute_j(histo, width):
    """
    takes histogram of counts, uses norm_histogram to convert to probabilties, it then calculates compute_j for one bin width

    :param histo: list
    :param width: float
    :return: float
    """

    sq_sum = 0;
    m = 0;
    k = 0;

    hist_p = norm_histogram(histo)

    hist_sq =[0] * len(hist_p)

    while k < len(hist_p):
        hist_sq[k] = hist_p[k] ** 2;
        sq_sum += (hist_sq[k]);
        m += histo[k];
        k += 1;

    j = 2/((m - 1) * width) - (((m + 1)/((m-1)*width))*(sq_sum))
    return(j);
    pass

def sweep_n(data, minimum, maximum, min_bins, max_bins):
    """
    find the optimal bin
    calculate compute_j for a full sweep [min_bins to max_bins]
    please make sure max_bins is included in your sweep

    :param data: list
    :param minimum: int
    :param maximum: int
    :param min_bins: int
    :param max_bins: int
    :return: list
    """
    optimal_hist = []

    b = min_bins

    while b <= max_bins:
        j = compute_j(plt.hist(data, b, (minimum,maximum))[0], (maximum-minimum)/b);

        optimal_hist.append(j)

        b += 1;

    return(optimal_hist)

    pass

def find_min(l):
    """
    generic function that takes a list of numbers and returns smallest number in that list its index.
    return optimal value and the index of the optimal value as a tuple.

    :param l: list
    :return: tuple
    """

    list_min = min(l);

    min_index = l.index(list_min);

    min_tuple = (list_min,min_index);

    return(min_tuple)

    pass

if __name__ == '__main__':
    data = np.loadtxt('input.txt')  # reads data from input.txt
    lo = min(data)
    hi = max(data)
    bin_l = 1
    bin_h = 100
    js = sweep_n(data, lo, hi, bin_l, bin_h)
    """
    the values bin_l and bin_h represent the lower and higher bound of the range of bins.
    They will change when we test your code and you should be mindful of that.
    """
    print(find_min(js))


'''
Source codes for Python Machine Learning By Example 3rd Edition (Packt Publishing)
Chapter 4 Predicting Online Ads Click-through with Tree-Based Algorithms
Author: Yuxi (Hayden) Liu (yuxi.liu.ece@gmail.com)
'''

import matplotlib.pyplot as plt
import numpy as np

# Plot Gini Impurity in binary case
pos_fraction = np.linspace(0.00, 1.00, 1000)
gini = 1 - pos_fraction**2 - (1-pos_fraction)**2
plt.plot(pos_fraction, gini)
plt.xlabel('Positive fraction')
plt.ylabel('Gini Impurity')
plt.ylim(0, 1)
# plt.show()

# Given labels of a data set, the Gini Impurity calculation function
def gini_impurity(labels):
    # When the set is empty, it is also pure
    if not labels:
        return 0
    # Count the occurrences of each label
    counts = np.unique(labels, return_counts=True)[1]
    fractions = counts / float(len(labels))
    return 1 - np.sum(fractions ** 2)

print(f'{gini_impurity([1, 1, 0, 1, 0]):.4f}')
print(f'{gini_impurity([1, 1, 0, 1, 0, 0]):.4f}')
print(f'{gini_impurity([1, 1, 1, 1]):.4f}')

# Plot entropy in binary case
pos_fraction = np.linspace(0.00, 1.00, 1000)
ent = - (pos_fraction * np.log2(pos_fraction) + (1 - pos_fraction) * np.log2(1 - pos_fraction))
plt.plot(pos_fraction, ent)
plt.xlabel('Positive fraction')
plt.ylabel('Entropy')
plt.ylim(0, 1)
# plt.show()

# Given labels of a data set, the entropy calculation function
def entropy(labels):
    if not labels:
        return 0
    counts = np.unique(labels, return_counts=True)[1]
    fractions = counts / float(len(labels))
    return - np.sum(fractions * np.log2(fractions))

print(f'{entropy([1, 1, 0, 1, 0]):.4f}')
print(f'{entropy([1, 1, 0, 1, 0, 0]):.4f}')
print(f'{entropy([1, 1, 1, 1]):.4f}')

# def information_gain(y, mask, func=entropy):
#     s1 = np.sum(mask)
#     s2 = mask.size - s1
#     if (s1 == 0 | s2 == 0): return 0
#     return func(y) - s1 / float(s1 + s2) * func(y[mask]) - s2 / float(s1 + s2) * func(y[np.logical_not(mask)])

criterion_function = {'gini': gini_impurity, 'entropy': entropy}
def weighted_impurity(groups, criterion='gini'):
    """
    Calculate weighted impurity of children after a split
    @param groups: list of children, and a child consists a list of class labels
    @param criterion: metric to measure the quality of a split, 'gini' for Gini Impurity or 'entropy' for Information Gain
    @return: float, weighted impurity
    """
    total = sum(len(group) for group in groups)
    weighted_sum = 0.0
    for group in groups:
        weighted_sum += len(group) / float(total) * criterion_function[criterion](group)
    return weighted_sum

children_1 = [[1, 0, 1], [0, 1]]
children_2 = [[1, 1], [0, 0, 1]]
print(f"Entropy of #1 split: {weighted_impurity(children_1, 'entropy'):.4f}")
print(f"Entropy of #2 split: {weighted_impurity(children_2, 'entropy'):.4f}")

def gini_impurity_np(labels):
    # When the set is empty, it is also pure
    if labels.size == 0:
        return 0
    # Count the occurrences of each label
    counts = np.unique(labels, return_counts=True)[1]
    fractions = counts / float(len(labels))
    return 1 - np.sum(fractions ** 2)

def entropy_np(labels):
    # When the set is empty, it is also pure
    if labels.size == 0:
        return 0
    counts = np.unique(labels, return_counts=True)[1]
    fractions = counts / float(len(labels))
    return - np.sum(fractions * np.log2(fractions))

criterion_function_np = {'gini': gini_impurity_np, 'entropy': entropy_np}
def weighted_impurity(groups, criterion='gini'):
    """
    Calculate weighted impurity of children after a split
    @param groups: list of children, and a child consists a list of class labels
    @param criterion: metric to measure the quality of a split, 'gini' for Gini Impurity or 'entropy' for Information Gain
    @return: float, weighted impurity
    """
    total = sum(len(group) for group in groups)
    weighted_sum = 0.0
    for group in groups:
        weighted_sum += len(group) / float(total) * criterion_function_np[criterion](group)
    return weighted_sum

def split_node(X, y, index, value):
    """
    Split data set X, y based on a feature and a value
    @param X: numpy.ndarray, dataset feature
    @param y: numpy.ndarray, dataset target
    @param index: int, index of the feature used for splitting
    @param value: value of the feature used for splitting
    @return: list, list: left and right child, a child is in the format of [X, y]
    """
    x_index = X[:, index]
    # if this feature is numerical
    if X[0, index].dtype.kind in ['i', 'f']:
        mask = x_index >= value
    # if this feature is categorical
    else:
        mask = x_index == value
    # split into left and right child
    left = [X[~mask, :], y[~mask]]
    right = [X[mask, :], y[mask]]
    return left, right

def get_best_split(X, y, criterion):
    """
    Obtain the best splitting point and resulting children for the data set X, y
    @param X: numpy.ndarray, dataset feature
    @param y: numpy.ndarray, dataset target
    @param criterion: gini or entropy
    @return: dict {index: index of the feature, value: feature value, children: left and right children}
    """
    best_index, best_value, best_score, children = None, None, 1, None
    for index in range(len(X[0])):
        for value in np.sort(np.unique(X[:, index])):
            groups = split_node(X, y, index, value)
            impurity = weighted_impurity([groups[0][1], groups[1][1]], criterion)
            if impurity < best_score:
                best_index, best_value, best_score, children = index, value, impurity, groups
    return {'index': best_index, 'value': best_value, 'children': children}

def get_leaf(labels):
    # Obtain the leaf as the majority of the labels
    return np.bincount(labels).argmax()

def split(node, max_depth, min_size, depth, criterion):
    """
    Split children of a node to construct new nodes or assign them terminals
    @param node: dict, with children info
    @param max_depth: int, maximal depth of the tree
    @param min_size: int, minimal samples required to further split a child
    @param depth: int, current depth of the node
    @param criterion: gini or entropy
    """
    left, right = node['children']
    del (node['children'])
    if left[1].size == 0:
        node['right'] = get_leaf(right[1])
        return
    if right[1].size == 0:
        node['left'] = get_leaf(left[1])
        return
    # Check if the current depth exceeds the maximal depth
    if depth >= max_depth:
        node['left'], node['right'] = get_leaf(left[1]), get_leaf(right[1])
        return
    # Check if the left child has enough samples
    if left[1].size <= min_size:
        node['left'] = get_leaf(left[1])
    else:
        # It has enough samples, we further split it
        result = get_best_split(left[0], left[1], criterion)
        result_left, result_right = result['children']
        if result_left[1].size == 0:
            node['left'] = get_leaf(result_right[1])
        elif result_right[1].size == 0:
            node['left'] = get_leaf(result_left[1])
        else:
            node['left'] = result
            split(node['left'], max_depth, min_size, depth + 1, criterion)
    # Check if the right child has enough samples
    if right[1].size <= min_size:
        node['right'] = get_leaf(right[1])
    else:
        # It has enough samples, we further split it
        result = get_best_split(right[0], right[1], criterion)
        result_left, result_right = result['children']
        if result_left[1].size == 0:
            node['right'] = get_leaf(result_right[1])
        elif result_right[1].size == 0:
            node['right'] = get_leaf(result_left[1])
        else:
            node['right'] = result
            split(node['right'], max_depth, min_size, depth + 1, criterion)

def train_tree(X_train, y_train, max_depth, min_size, criterion='gini'):
    """
    Construction of a tree starts here
    @param X_train: list of training samples (feature)
    @param y_train: list of training samples (target)
    @param max_depth: int, maximal depth of the tree
    @param min_size: int, minimal samples required to further split a child
    @param criterion: gini or entropy
    """
    X = np.array(X_train)
    y = np.array(y_train)
    root = get_best_split(X, y, criterion)
    split(root, max_depth, min_size, 1, criterion)
    return root

CONDITION = {'numerical': {'yes': '>=', 'no': '<'},
             'categorical': {'yes': 'is', 'no': 'is not'}}
def visualize_tree(node, depth=0):
    if isinstance(node, dict):
        if node['value'].dtype.kind in ['i', 'f']:
            condition = CONDITION['numerical']
        else:
            condition = CONDITION['categorical']
        print('{}|- X{} {} {}'.format(depth * '  ', node['index'] + 1, condition['no'], node['value']))
        if 'left' in node:
            visualize_tree(node['left'], depth + 1)
        print('{}|- X{} {} {}'.format(depth * '  ', node['index'] + 1, condition['yes'], node['value']))
        if 'right' in node:
            visualize_tree(node['right'], depth + 1)
    else:
        print(f"{depth * '  '}[{node}]")

X_train = [['tech', 'professional'],
           ['fashion', 'student'],
           ['fashion', 'professional'],
           ['sports', 'student'],
           ['tech', 'student'],
           ['tech', 'retired'],
           ['sports', 'professional']]

y_train = [1,
           0,
           0,
           0,
           1,
           0,
           1]

tree = train_tree(X_train, y_train, 2, 2)
visualize_tree(tree)

X_train_n = [[6, 7],
           [2, 4],
           [7, 2],
           [3, 6],
           [4, 7],
           [5, 2],
           [1, 6],
           [2, 0],
           [6, 3],
           [4, 1]]

y_train_n = [0,
           0,
           0,
           0,
           0,
           1,
           1,
           1,
           1,
           1]

tree = train_tree(X_train_n, y_train_n, 2, 2)
visualize_tree(tree)

from sklearn.tree import DecisionTreeClassifier
tree_sk = DecisionTreeClassifier(criterion='gini', max_depth=2, min_samples_split=2)
tree_sk.fit(X_train_n, y_train_n)

from sklearn.tree import export_graphviz
export_graphviz(tree_sk, out_file='tree.dot', feature_names=['X1', 'X2'], impurity=False, filled=True, class_names=['0', '1'])