import functools

import numpy as np
import sympy
from matplotlib import pyplot as plt, animation
from matplotlib.animation import FuncAnimation
from numpy import sin, cos
from scipy.integrate import odeint

def get_expressions():
    """
    Create symoblic expressions for the system.

    Returns:
        Expressions for theta_dd_1 and theta_dd_2.
    """

    s = 'l_1 theta_dd_1 m_1 m_2 l_2 theta_dd_2 theta_1 theta_2 theta_d_2 theta_d_1 g'
    l_1, theta_dd_1, m_1, m_2, l_2, theta_dd_2, theta_1, theta_2, theta_d_2, theta_d_1, g = sympy.symbols(s)
    expr1 = l_1 * theta_dd_1 * (m_1 + m_2) + m_2 * l_2 * (theta_dd_2 * sympy.cos(theta_1 - theta_2) + theta_d_2 ** 2 * sympy.sin(theta_1 - theta_2)) + (m_1 + m_2) * g * sympy.sin(theta_1)
    expr2 = m_2 * l_2 * theta_dd_2 + m_2 * l_1 * (theta_dd_1 * sympy.cos(theta_1 - theta_2) - theta_d_1 ** 2 * sympy.sin(theta_1 - theta_2)) + m_2 * g * sympy.sin(theta_2)
    theta_dd_1_expr = sympy.solveset(expr1, theta_dd_1).args[0]
    theta_dd_2_eval = sympy.solveset(expr2.subs(theta_dd_1, theta_dd_1_expr), theta_dd_2).args[0]
    theta_dd_2_expr = sympy.solveset(expr2, theta_dd_2).args[0]
    theta_dd_1_eval = sympy.solveset(expr1.subs(theta_dd_2, theta_dd_2_expr), theta_dd_1).args[0]

    res1 = sympy.simplify(theta_dd_1_eval)
    res2 = sympy.simplify(theta_dd_2_eval)

    sympy.init_printing()

    print(res1, res2, sep='\n\n')

    return res1, res2

def double_pend(
    y: list,
    t: float,
    m_1: float,
    l_1: float,
    m_2: float,
    l_2: float,
    g: float
) -> list:
    """
    Calculate one step for coupled pendulums.

    Args:
        y: (theta_1, theta_d_1, theta_2, theta_d_2) at this timestep.
        t: Time (unused).
        m_1: Mass 1.
        l_1: Length of pendulum 1.
        m_2: Mass 2.
        l_2: Length of pendulum 2.
        g: Gravity constant.

    Returns:
        Derivative estimates for each element of y.
    """

    theta_1, theta_d_1, theta_2, theta_d_2 = y

    # From get_expressions()
    theta_dd_1 = (g*m_1*sin(theta_1) + g*m_2*sin(theta_1)/2 + g*m_2*sin(theta_1 - 2*theta_2)/2 + l_1*m_2*theta_d_1**2*sin(2*theta_1 - 2*theta_2)/2 + l_2*m_2*theta_d_2**2*sin(theta_1 - theta_2))/(l_1*(-m_1 + m_2*cos(theta_1 - theta_2)**2 - m_2))
    theta_dd_2 = (-g*m_1*sin(theta_2) + g*m_1*sin(2*theta_1 - theta_2) - g*m_2*sin(theta_2) + g*m_2*sin(2*theta_1 - theta_2) + 2*l_1*m_1*theta_d_1**2*sin(theta_1 - theta_2) + 2*l_1*m_2*theta_d_1**2*sin(theta_1 - theta_2) + l_2*m_2*theta_d_2**2*sin(2*theta_1 - 2*theta_2))/(2*l_2*(m_1 - m_2*cos(theta_1 - theta_2)**2 + m_2))

    dydt = [theta_d_1, theta_dd_1, theta_d_2, theta_dd_2]

    return dydt

def plot(
    y0: list,
    t: np.ndarray,
    system_params: dict,
    trajectory: bool = True,
    angles: bool = False,
    show: bool = True,
    save: str = ''
):
    """
    Animate the coupled pendulums.

    Args:
        y0: Initial conditions for (theta_1, theta_d_1, theta_2, theta_d_2).
        t: Times to evaluate equations of motion.
        system_params: Physical parameters of the system.
        trajectory: Animate trajectory.
        angles: Plot angles over time.
        show: Display trajectory animation.
        save: Filepath to save trajectory animation.

    """

    pend_func = functools.partial(double_pend, **system_params)

    theta_1, theta_d_1, theta_2, theta_d_2 = odeint(pend_func, y0, t).T

    if angles:
        theta_1 = theta_1 % np.pi
        theta_2 = theta_2 % np.pi

        plt.plot(t, theta_1)
        plt.plot(t, theta_2)
        plt.show()

    if trajectory:

        l_1, l_2 = system_params['l_1'], system_params['l_2']

        x_1 = l_1 * np.sin(theta_1)
        y_1 = l_1 * np.cos(theta_1)
        x_2 = x_1 + l_2 * np.sin(theta_2)
        y_2 = y_1 + l_2 * np.cos(theta_2)

        fig, ax = plt.subplots()

        ln, = ax.plot([], [], 'o-', lw=2)

        def init():
            bound = l_1 + l_2
            ax.set_xlim(-bound, bound)
            ax.set_ylim(-bound, bound)
            ln.set_data([], [])
            return ln,

        def update(vals):
            ln.set_data(vals[:3], vals[3:])
            return ln,

        frames = np.stack([np.zeros_like(x_1), x_1, x_2, np.zeros_like(y_1), -y_1, -y_2], axis=-1)

        ani = FuncAnimation(fig, update, init_func=init, frames=frames, blit=True, interval=25, repeat=False)

        if save:
            Writer = animation.writers['ffmpeg']
            writer = Writer(fps=40, metadata=dict(artist='Me'), bitrate=1800)

            ani.save(save, writer=writer)

        if show:
            plt.show()

if __name__ == '__main__':

    system_params = {
        'm_1': 1,
        'l_1': 1,
        'm_2': 1,
        'l_2': 1,
        'g': 9.81
    }

    # Initial conditions for (theta_1, theta_d_1, theta_2, theta_d_2)
    y0 = [np.pi / 2, 0, np.pi, 0]

    t = np.linspace(0, 20, 1000)

    plot(y0, t, system_params)

import numpy,from numpy,from scipy,import sympy


import numpy as np
import matplotlib.pyplot as plt
from numpy.core.multiarray import ndarray
from numpy import sign
from typing import Optional, Union, Tuple

"""
1	Topic Introduction
--------------------------
Determine the roots of f(x) = -12-21x-18x&2-2.75x^3 through various root finding techniques.

2	Topic Theory/Approach
--------------------------
Utilize incremental function to search the roots as a sense of where the roots are.
Use bisection and false position functions to estimate the roots.

3	Problems
--------------------------
    (a)	Problem Statement

Determine the roots of f(x) = 􀀀12 􀀀 21x 􀀀 18x2 􀀀 2:75x3 between xl = 􀀀10 and xu = 10, and a
stopping criterion of 0.1%. You should use your icremental function rst to nd all roots, and use
those ranges to get better results using the bisection and false position functions.

    (b)	Problem Approach
Utilize bisection and false position techinques with the lower x of -10 and upper x of 10 with a stopping
criterion of .001. The first

    (c)	Problem Results

Incremental:	                -5.25000000000019, -5.24900000000019
Bisection Root Estimate:	    -5.24901, -0.0
False Position Root Estimate:	-5.25927, 0.61139

    (d)	Problem Discussion of Results
The incremental function returns the range in where the roots reside.

The Bisection Root result returns the estimated root of the function
between the range of -10 and 10 and a stopping criteria of 100 iterations.

The False Position result of a trial and error technique to determine the root
of the function and adjusts the values until a criteria is met or if the root is found.

"""

def incremental(f, xa, xb, dx):
    x1 = xa
    f1 = f(xa)

    x2 = xa + dx
    f2 = f(x2)

    while sign(f1) == sign(f2):
        if x1 >= xb:
            return None, None
        x1 = x2
        f1 = f2
        x2 = x1 + dx
        f2 = f(x2)
    else:
        print('Incremental:\t' + str(x1) + ', ' + str(x2))
        return x1, x2

def false_position(f, xa, xb):
    x_lp = xa
    x_up = xb

    for i in range(1, 21, 1):
        x_rp = x_up - ((f(x_up) * (x_lp - x_up)) / (f(x_lp) - f(x_up)))
        if f(x_lp) * f(x_rp) < 0:
            x_up = x_rp
        else:
            x_lp = x_rp
    print('False Position Root Estimate:\t' + str(round(x_rp, 5)) + ', ' + str(round(f(x_rp), 5)))
    plt.plot(x_rp, f(x_rp), 'b*')

def bisection(f, xa, xb):
    x_lower = xa
    x_upper = xb
    x_r = x_lower
    for i in range(1, 100, 1):
        x_r = ((x_upper + x_lower) / 2)
        if (f(x_lower) * f(x_r) < 0):
            x_upper = x_r
        else:
            x_lower = x_r
    print('Bisection Root Estimate:\t' + str(round(x_r, 5)) + ', ' + str(round(f(x_r), 5)))
    plt.plot(x_r, f(x_r), 'g*')

if __name__ == "__main__":
    x: Union[ndarray, Tuple[ndarray, Optional[float]]] = np.linspace(-10, 10)
    y = lambda x: -12 - 21 * x - 18 * (x ** 2) - 2.75 * (x ** 3)

    line, = plt.plot(x, y(x), 'r')
    line.set_antialiased(False)  # turn off antialising

    plt.setp(line, 'linewidth', 1.0)

    roots1, roots2 = incremental(y, -10, 10, .001)
    bisection(y, -10, 10)
    false_position(y, -10, 10)

    plt.legend((line, ), ('f(x) = -12 - 21x - 18x^2 - 2.75x^3',))
    plt.grid(True)
    plt.show()


### Interpolation

import numpy as np
from matplotlib import pylab as plt

# Ex 3.1
def l(q, i, x):
    """returns the ith Lagrangian polynomial associated to the grid q,
    evaluated at x"""
    return np.prod([(x - q[j])/(q[i] - q[j])
                    for j in range(len(q)) if j!=i])

# Ex 3.2
def L(q, g, x):
    """returns the Lagrangian interpolation of g associated to the grid q,
    evaluated at x"""
    return sum([g(q[i])*l(q,i,x) for i in range(len(q))])

# Ex 3.3 - 3.4
for N in range(1, 14, 4):
    grid = np.linspace(0, 1, N)
    plt.plot([L(list(grid), lambda t : 1/(1 + (10*t - 5)**2), x)
              for x in np.linspace(0, 1, 1000)])
    plt.plot([1/(1 + (10*x - 5)**2) for x in np.linspace(0, 1, 1000)])
    plt.title("Degree "+ str(N) +" lagrangian approximation vs Runge function")
    plt.show()

#Ex 3.5
import numpy.polynomial as P

def lP(q, i):
    """returns the ith Lagrangian polynomial associated to the grid q,
    implemented with numpy.polynomial"""
    lPoly = P.Polynomial([1])
    for j in range(len(q)):
        if j!=i:
            den = (q[i]-q[j]) # the denominator of lagrangian polynomials
            lPoly *= P.Polynomial([1/den, -q[i]/den])
    return lPoly

def LP(q, g):
    """returns the Lagrangian interpolation of g associated to the grid q,
    implemented with numpy.polynomial"""
    LPoly = P.Polynomial([0])
    for j in range(len(q)):
        LPoly += g(q[j]) * lP(q,j)
    return LPoly

evalPoints = [h * 2**(-6) for h in range(2**6)]
interPoints = [h * 2**(-2) for h in range(2**2)]
DlP = lP(interPoints, 1).deriv(2)
# The 2nd lagrange polynomial with 4 interpolation points in [0,1] is
# 32 - 24x + 6x^2 - .5x^3; so its 2nd derivative should be 12 - 3x
plt.plot(evalPoints, [DlP(x) for x in evalPoints])
plt.title("2nd derivative of 32-24x+6x^2-.5x^3; indeed it's 12-3x")
plt.show()

# Ex 3.6
def Lambda(q, x):
    return sum([abs(l(q, i, x)) for i in range(len(q))])

for N in range(1, 14, 4):
    plt.plot([Lambda(np.linspace(0, 1, N), x)
              for x in np.linspace(0, 1, 1000)])
    plt.title("Degree " + str(N) + " Lambda function")
    plt.show()
# The oscillations on the edges get bigger as N increases.
# We can damp the oscillations adding more point on the edges;
# a quantitative estimate on where and how many points to add
# to obtain the "best" dampening is given by Chebyshev nodes.

# Ex 3.7
from scipy.optimize import minimize_scalar

def LambdaMaxList(q):
    """returns an approximation of the max of the Lambda function
    and the point in which it is attained, using minize_scalar from scipy
    in each interval of the grid q"""
    gridMax = [0, 0]

    for j in range(len(q)-1):
        # We will use minimize_scalar looping on all intervals [q[j], q[j+1]]
        start = q[j]
        end = q[j+1]
        midpoint = (q[j]+q[j+1])/2

        localMinInfo = minimize_scalar(
            lambda x : -Lambda(q,x),
            bracket = (start, midpoint, end))

        localMaxValue = abs(localMinInfo.get("fun"))
        if localMaxValue > gridMax[0]:
            gridMax = [localMaxValue, localMinInfo.get("x")]
            # At each step we check, and eventually update, where Lambda
            # is maximum and the point in which the max is attained

    return gridMax

# Ex 3.8
def greedyStepMinimizeLambda(M, q):
    """given a starting grid q of [0,1], returns the M grid points
    obtained by adding points to q, at each addition requiring that
    a new point is placed where Lambda is maximum"""
    for j in range(M):
        q.append(LambdaMaxList(q)[1])
    return q

startGrid = [0, 0.5, 1]   # The starting grid of points
NPoints = 15              # Number of points which will be added to the grid
finalGrid = greedyStepMinimizeLambda(NPoints, startGrid)
plt.plot(finalGrid, [0 for i in range(len(finalGrid))] ,'ro')
plt.title("Greedy step minimization for "
          + str(len(finalGrid)) + " nodes")
plt.show()
# As the graph shows, indeed the nodes tend to concetrate on the edges


import numpy as np
import matplotlib.pyplot as plt

n = 20
mean = 30
variance = 20
a = 1
b = 1
c = 3
d = 50

def gibbs_sampler(size):
    mu_set = np.ndarray(size)
    tau_set = np.ndarray(size)
    mu, tau = 3, 1  # initial values of mu and tau
    para_a = a + n / 2
    for i in range(size):
        para_b = b + ((n - 1) * variance + n * ((mean - mu) ** 2)) / 2
        tau = np.random.gamma(para_a, para_b)
        para_c = (c * d + n * tau * mean) / (n * tau + d)
        para_d = 1 / (n * tau + d)
        mu = np.random.normal(para_c, np.sqrt(para_d))

        mu_set[i] = mu
        tau_set[i] = tau

    return mu_set, tau_set

samples = gibbs_sampler(10000)

def plot_joint(samples):
    index = [i for i in range(len(samples[0])) if 29.94 < samples[0][i] < 30.01 and samples[1][i] < 5000]
    x = [samples[0][i] for i in index]
    y = [samples[1][i] for i in index]
    plt.scatter(x, y, alpha=0.02, color='black', linewidths=0.5)
    plt.title('Joint distribution for mu and tau')
    plt.xlabel('mu')
    plt.ylabel('tau')
    plt.show()

def plot_marginal_mu(samples):
    index = [i for i in range(len(samples[0])) if 29.94 < samples[0][i] < 30.01 and samples[1][i] < 5000]
    x = [samples[0][i] for i in index]
    plt.hist(x, bins=60, rwidth=0.8)
    plt.title('Marginal distribution for mu')
    plt.xlabel('mu')
    plt.show()

def plot_marginal_tau(samples):
    index = [i for i in range(len(samples[0])) if 29.94 < samples[0][i] < 30.01 and samples[1][i] < 5000]
    x = [samples[1][i] for i in index]
    plt.hist(x, bins=60, rwidth=0.9)
    plt.title('Marginal distribution for tau')
    plt.xlabel('tau')
    plt.show()

def plot_marginal_variance(samples):
    index = [i for i in range(len(samples[0])) if 29.94 < samples[0][i] < 30.01 and samples[1][i] < 5000]
    x = [1/samples[0][i] for i in index]
    plt.hist(x, bins=60, rwidth=0.9)
    plt.title('Marginal distribution for variance')
    plt.xlabel('variance')
    plt.show()

# plot_joint(samples)
plot_marginal_mu(samples)
# plot_marginal_tau(samples)
# plot_marginal_variance(samples)


""" NumPy is a Linear Algebra Library for Python, the reason it is so important for Data
    Science with Python is that almost all of the libraries in the PyData Ecosystem rely on
    NumPy as one of their main building blocks.

    Numpy is also incredibly fast, as it has bindings to C libraries. For more info on why
    you would want to use Arrays instead of lists

    Numpy has many built-in functions and capabilities.
    Exemplos de utilização do Numpy: vectors, arrays, matrices, and number generation.
    Arrays: vetores (1-D) e matrizes(2-D).

    conda install numpy    or      pip install numpy

    User Guide:     https://docs.scipy.org/doc/numpy-1.15.0/numpy-user-1.15.0.pdf """

import numpy as np
# ________________________________________________ Criar Arrays
# Array a partir de uma lista
lista = [1,2,3,4]
print(lista)
np.array(lista)
ar_lista = np.array(lista)
print(ar_lista)
array = np.array([5,3,9])
print(array)

matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(matriz)
np.array(matriz)

# Built-in Methods: built-in to generate Arrays
np.arange(10)               # Return evenly spaced values within a given interval."
np.arange(1,10,0.5)         # start(inclusive), stop(exclusive), step

# Random: numpy tem varias formas de criar random number arrays
np.random.rand(2)          # Create an array of the given shape and populate it with random samples from a uniform distribution over [0, 1)
np.random.rand(3,5)

np.random.randn(2)          # Return a sample (or samples) from the standard normal distribution
np.random.randn(6,6)

np.random.randint(10)
np.random.randint(1,100)    # Return random integers from low (inclusive) to high(exclusive)
np.random.randint(1,100,10)

## ________________________________________________ Array Attributes and Methods
np.random.seed(303)         # todos usarem os mesmos numeros aleatorios
arr = np.arange(10)
a1 = np.random.randint(0,50,10)

print(a1)
a1.max()
a1.min()
a1.argmin()
a1.argmax()

# Shape:  is an attribute that arrays have (not a method):"
a1.shape
a1.reshape(2,5)
a8 = np.array([[5,7],[7,9],[9,11],[11,13],[13,15]])
a1.reshape(a8.shape)

# ________________________________________________ Array indexing / SLICE
d = np.arange(0,11)
c = np.arange(10,21)
print( 'c: ', c,'\nd: ', d)
d[2]
d[-1]
#  !!!!! começa sempre com indice 0
d[3:6]  # [ inicio(inclusive):fim(exclusive)]
c[8]
c[2:5]
c[0:7]
c[:7]
d[5:]
d[::-1]

a3 = np.arange(0,11)
a3 > 5   #boolean
bol_a3 = a3>5
a3[bol_a3] # retorna somente os valores com 'True'
a3[a3>5]
a4 = a3[a3>5]

#Funções para Array

np.sqrt(array)
np.exp(2)
np.exp(array)
np.max(array)
array.max()

np.pi
np.sin(10)              # radianos
np.sin(90*np.pi/180)    # 90 graus
np.sin(array)
graus = np.arange(0,361,90)
np.sin(graus)
np.sin(graus*np.pi/180)
np.rint(np.sin(graus*np.pi/180))

# https://docs.scipy.org/doc/numpy/reference/ufuncs.html


# https://github.com/sympy/sympy/wiki/Generating-tables-of-derivatives-and-integrals

from sympy import *
t, dt, n, w = symbols('t dt n w', real=True)

# Finite difference operators

def D_t_forward(u):
    return (u(t + dt) - u(t))/dt

def D_t_backward(u):
    return (u(t) - u(t-dt))/dt

def D_t_centered(u):
    return (u(t + dt/2) - u(t-dt/2))/dt

def D_2t_centered(u):
    return (u(t + dt) - u(t-dt))/(2*dt)

def D_t_D_t(u):
    return (u(t + dt) - 2*u(t) + u(t-dt))/(dt**2)

op_list = [D_t_forward, D_t_backward,
           D_t_centered, D_2t_centered, D_t_D_t]

def ft1(t):
    return t

def ft2(t):
    return t**2

def ft3(t):
    return t**3

def f_expiwt(t):
    return exp(I*w*t)

def f_expwt(t):
    return exp(w*t)

func_list = [ft1, ft2, ft3, f_expiwt, f_expwt]
import inspect

for func in func_list:
    print(('\n--- Function:', inspect.getsource(func), '---'))
    for op in op_list:
        print(('\nOperator:', op.__name__))
        f = func
        e = op(f)
        e = simplify(expand(e))
        print(('simplify(expand(operator(function)):', e))
        if func in [f_expiwt, f_expwt]:
            e = e/f(t)
        e = e.subs(t, n*dt)
        print(('t -> n*dt:', expand(e)))
        print(('factor(simplify(expand(e))):', factor(simplify(expand(e)))))


#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# =========================================================================== #
# Project : ML Studio                                                         #
# Version : 0.1.0                                                             #
# File    : activation.py                                                     #
# Python  : 3.8.2                                                             #
# --------------------------------------------------------------------------  #
# Author  : John James                                                        #
# Company : DecisionScients                                                   #
# Email   : jjames@decisionscients.com                                        #
# URL     : https://github.com/decisionscients/MLStudio                       #
# --------------------------------------------------------------------------  #
# Created       : Saturday, May 16th 2020, 11:39:45 pm                        #
# Last Modified : Saturday, May 16th 2020, 11:39:45 pm                        #
# Modified By   : John James (jjames@decisionscients.com)                     #
# --------------------------------------------------------------------------  #
# License : BSD                                                               #
# Copyright (c) 2020 DecisionScients                                          #
# =========================================================================== #
"""Activation functions."""
from abc import ABC, abstractmethod

import numpy as np
from sklearn.base import BaseEstimator

from mlstudio.utils.validation import validate_range
# --------------------------------------------------------------------------  #
# Functions adapted from the following sources.
# Reference https://en.wikipedia.org/wiki/Activation_function
# Reference https://www.python-course.eu/softmax.php
# --------------------------------------------------------------------------  #
class Activation(ABC, BaseEstimator):
    """Base class for activation classes."""
    @abstractmethod
    def __call__(self, x):
        pass

    @abstractmethod
    def gradient(self, x):
        pass

class Sigmoid(Activation):
    """Sigmoid activation function."""

    def __call__(self, x):
        if np.all(x >= 0):
            s = 1 / (1 + np.exp(-x))
        else:
            s =  np.exp(x) / (1 + np.exp(x))
        return s

    def gradient(self, x):
        return self.__call__(x) * (1 - self.__call__(x))

class Softmax(Activation):
    def __call__(self, x):
        # Subtract the max to avoid underflow or overflow errors
        e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
        s = e_x / np.sum(e_x, axis=-1, keepdims=True)
        return s

    def gradient(self, x):
        p = self.__call__(x)
        return p * (1 - p)

class TanH(Activation):
    def __call__(self, x):
        return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-1))

    def gradient(self, x):
        return 1 - np.power(self.__call__(x), 2)

class ReLU(Activation):
    def __call__(self, x):
        return np.where(x > 0, x, 0)

    def gradient(self, x):
        return np.where(x > 0, 1, 0)

class LeakyReLU(Activation):
    def __init__(self, alpha=0.01):
        self.alpha = alpha

    def __call__(self, x):
        return np.where(x >= 0, x, self.alpha * x)

    def gradient(self, x):
        return np.where(x >= 0, 1, self.alpha)


import re
import numpy as np

def split_equation(equation):
    num_set = ['0','1','2','3','4','5','6','7','8','9','%', '.', 'P', 'I']
    start = ''
    equ_list = []
    for char in equation:
        if char not in num_set:
            if start != '':
                equ_list.append(start)
            equ_list.append(char)
            start = ''
        else:
            start += char
    if start != '':
        equ_list.append(start)
    return equ_list

def postfix_equation(equ_list):
    stack = []
    post_equ = []
    op_list = ['+', '-', '*', '/', '^']
    priori = {'^':3, '*':2, '/':2, '+':1, '-':1}
    for elem in equ_list:
        if elem == '(':
            stack.append('(')
        elif elem == ')':
            while 1:
                op = stack.pop()
                if op == '(':
                    break
                else:
                    post_equ.append(op)
        elif elem in op_list:
            while 1:
                if stack == []:
                    break
                elif stack[-1] == '(':
                    break
                elif priori[elem] > priori[stack[-1]]:
                    break
                else:
                    op = stack.pop()
                    post_equ.append(op)
            stack.append(elem)
        else:
            #if elem == 'PI':
            #    post_equ.append('3.14')
            #else:
            #    post_equ.append(elem)
            post_equ.append(elem)
    while stack != []:
        post_equ.append(stack.pop())
    return post_equ

def post_solver(post_equ):
    stack = []
    op_list = ['+', '-', '/', '*', '^']
    for elem in post_equ:
        if elem not in op_list:
            op_v = elem
            if '%' in op_v:
                op_v = float(op_v[:-1])/100.0
            stack.append(str(op_v))
        elif elem in op_list:
            op_v_1 = stack.pop()
            op_v_1 = float(op_v_1)
            op_v_2 = stack.pop()
            op_v_2 = float(op_v_2)
            if elem == '+':
                stack.append(str(op_v_2+op_v_1))
            elif elem == '-':
                stack.append(str(op_v_2-op_v_1))
            elif elem == '*':
                stack.append(str(op_v_2*op_v_1))
            elif elem == '/':
                stack.append(str(op_v_2/op_v_1))
            else:
                stack.append(str(op_v_2**op_v_1))
    return stack.pop()

def solve_equation(equ_list):
    post_equ = postfix_equation(equ_list)
    ans = post_solver(post_equ)
    return ans

def equ_api_1(equ, ans):
    '''
    template: list, x = xxxxx
    return boolean variable
    '''
    equ = ''.join(equ)
    equ = equ[2:]
    equ = split_equation(equ)
    p_ans = solve_equation(equ)
    #print p_ans
    if abs(float(p_ans) - float(ans))<1e-5:
        return True
    return False


# the tools I used work properly --> reliability
# the result I got by using analysis tools is what I want --> validity
from coefficient import coefficient
import numpy as np
import pandas as pd
# retest reliability
def retest_reliablity():
    """check whether a test is reliable in different period"""
    X1 = np.array([54, 67, 67, 83, 87, 89, 84, 90, 98, 65])
    X2 = np.array([56, 77, 87, 89, 89, 90, 87, 92, 99, 76])
    r = coefficient(X1, X2)
    return r

def parallel_forms_reliability():
    """check whether the same equality and similarity of the test
    on different parallel forms.
    """
    X1 = np.array([4, 5, 3, 6, 7, 5, 6, 4, 3, 3])
    X2 = np.array([5, 6, 5, 6, 7, 6, 7, 8, 7, 7])
    r = coefficient(X1, X2)
    return r

def internal_consisitency_reliability():
    """ using coefficient of Cronbach's
                     k       s_y^2 - sum(s_i^2)
        epsilon = ------- * --------------------
                   k - 1           s_y^2

    """
    data = {
        'proj1': [3, 4, 3, 3, 3, 4, 2, 3, 3, 3],
        'proj2': [5, 4, 4, 3, 4, 5, 5, 4, 5, 3],
        'proj3': [1, 3, 4, 5, 5, 5, 5, 4, 4, 2],
        'proj4': [4, 5, 4, 2, 4, 3, 3, 2, 4, 3],
        'proj5': [1, 3, 4, 1, 3, 2, 4, 4, 3, 2],
    }
    df = pd.DataFrame(data=data, index=range(1, 11))
    k = df.shape[1]
    s_y = df.sum(axis=1).var()
    sum_of_s_i = df.var(axis=0).sum()
    epsilon = (k / (k - 1)) * ((s_y - sum_of_s_i) / s_y)
    return epsilon

def interrater_reliability():
    """determine whether two interrater are consistent with
    observation
    """
    X1 = np.array([1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1])
    X2 = np.array([1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1])
    r = (X1 == X2).sum() / X1.shape[0]
    return r

# content validity: whether the content and reflect the target?
# criterion validity: whether this index is relative to others?
# construct validity:


import math,time
import numpy as np

def great_circle(lon1,lat1,lon2,lat2):
    radius = 3956 #miles
    x = math.pi/180.0

    a = (90.0-lat1)*(x)
    b = (90.0-lat2)*(x)
    theta = (lon2-lon1)*(x)
    c = math.acos((math.cos(a)*math.cos(b)) +
                  (math.sin(a)*math.sin(b)*math.cos(theta)))
    return radius*c

def great_circle_numpy(mat):
    """numpys great circle"""
    radius = 3956
    x = np.pi/180.0
    lon1 = mat[:,0]
    lat1 = mat[:,1]
    lon2 = mat[:,2]
    lat2 = mat[:,3]

    a = (90.0-lat1)*(x)
    b = (90.0-lat2)*(x)
    theta = (lon2-lon1)*(x)
    c = np.arccos((np.cos(a)*np.cos(b)) +
                  (np.sin(a)*np.sin(b)*np.cos(theta)))
    return radius*c

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

    ## create the matrix
    lon1,lat1,lon2,lat2 = 42,0.5,-13,-32
    n = int(1e06)
    mat = np.zeros((n,4),)
    mat = mat + [lon1,lat1,lon2,lat2]

    for i in range(mat.shape[0]):
        x = great_circle(*mat[i,:])

    runTime1 = time.time() - timeStart
    print('The run time by for loop = ', time.strftime('%H:%M:%S', time.gmtime(runTime1)))

    timeStart = time.time()
    c = great_circle_numpy(mat)
    runTime2 = time.time() - timeStart
    print('The run time by using numpy = ',time.strftime('%H:%M:%S', time.gmtime(runTime2)))
    return runTime1, runTime2

if __name__=='__main__':
    main()


from scipy import linalg as lg
import numpy as np

arr = np.array([
    [1, 2],
    [3, 4]
])

# Matrix arr's determinant
print('Det: ', lg.det(arr))
# Matrix arr's inverse
print('Inv: ', lg.inv(arr))

# Solve linear equations
"""
|1,2||x1|_|6 |
|3,4||x2|-|14|

the solution is x1 = 2, x2 = 2
"""
b = np.array([6, 14])
print('Sol: ', lg.solve(arr, b))

# Matrix arr's eigenvalue ['aɪdʒənˌvælju:]
print('Eig: ', lg.eig(arr))

# Matrix LU factorisation
print('LU decomposition')
P, L, U = lg.lu(arr)
print("L:  \n", L)
print("U:  \n", U)


import numpy as np
import matplotlib.pyplot as plt
from pprint import pprint

# declare vars
h = 50  # height
N = 10000
beta = np.pi
theta1 = np.linspace(0, np.pi, N)
theta2 = np.linspace(np.pi, 2 * np.pi, N)

def func1(angle):
    fixed_var = (h / (4 + np.pi))
    if 0 <= angle <= (1 / 8) * beta:
        return fixed_var * \
               (np.pi * angle / beta - 0.25 * np.sin(4 * np.pi * angle / beta))
    elif (1 / 8) * beta < angle <= (7 / 8) * beta:
        return fixed_var * \
               (2 + np.pi * angle / beta - 9 * np.sin(4 * np.pi * angle / (3 * beta) + np.pi / 3) / 4)
    elif (7 / 8) * beta < angle <= beta:
        return fixed_var * \
               (4 + np.pi * angle / beta - np.sin(4 * np.pi * angle / beta) / 4)

def func2(angle):
    fixed_var = (h / (4 + np.pi))
    if 0 <= angle <= (1 / 8) * beta:
        return h - fixed_var * \
               (np.pi * angle / beta - 0.25 * np.sin(4 * np.pi * angle / beta))
    elif (1 / 8) * beta < angle <= (7 / 8) * beta:
        return h - fixed_var * \
               (2 + np.pi * angle / beta - 9 * np.sin(4 * np.pi * angle / (3 * beta) + np.pi / 3) / 4)
    elif (7 / 8) * beta < angle <= beta:
        return h - fixed_var * \
               (4 + np.pi * angle / beta - np.sin(4 * np.pi * angle / beta) / 4)

if __name__ == '__main__':
    y1 = list()
    y2 = list()
    x1 = list()
    x2 = list()
    y_all_1th_diff = list()
    y_all_2th_diff = list()

    for angle in theta1:
        y1.append(func1(angle))
        # y2.append(func2(angle))

    for i in range(len(y1)):
        if i > 0:
            y_all_1th_diff.append((y1[i] - y1[i - 1]) / (theta1[i] - theta1[i - 1]))
            x1.append(theta1[i])
        else:
            y_all_1th_diff.append(0)
            x1.append(0)

    for i in range(len(y_all_1th_diff)):
        if i > 0:
            y_all_2th_diff.append((y_all_1th_diff[i] - y_all_1th_diff[i - 1]) / (x1[i] - x1[i - 1]))
            x2.append(x1[i])
        else:
            y_all_2th_diff.append(0)
            x2.append(0)

    print(len(y_all_2th_diff))

    plt.plot(x2, y_all_2th_diff)
    # plt.title(r'height = 50mm, $\beta$ = 180$^\circ$')
    # plt.xlabel(r'$\theta$')
    plt.grid()
    # plt.ylabel('mm')
    plt.show()


import numpy as np

#1. numpy calculation
a=np.arange(1,10).reshape(3,3)
b=np.arange(10,19).reshape(3,3)
print(a)
print(b)
#所有元素都加1
print(a+1)

#所有元素都平方
print(a**2)

#判斷式輸出boolen
print(a<5)

#相對應的元素相加
print(a*b)

#矩陣內積
print(np.dot(a,b))

#2. function i numpy and statistic
c=np.arange(1,10).reshape(3,3)
print(c)
#[[1 2 3]
# [4 5 6]
# [7 8 9]]

#最小值與最大值
print(np.min(c),np.max(c))                  #1 9

#總和 乘積 平均
print(np.sum(c),np.product(c),np.mean(c))   #45 362880 5.0

#標準差
print(np.std(c))                            #2.581988897471611

#變異數
print(np.var(c))                            #6.666666666666667

#中位數
print(np.median(c))                         #5.0

#max-min
print(np.ptp(c))                            #8

#3. sort in numpy
d=np.random.choice(50,size=10,replace=False)
#before sorting
print(d)
#after sorting
print(np.sort(d))
#the index after sorting
print(np.argsort(d))
#use the index to get the value
for i in np.argsort(d):
    print(d[i],end=" ")
print("\n")

#N dimension array sorting
e=np.random.randint(0,10,(3,5))
print(e)
#對每一直行sort
print(np.sort(e,axis=0))
#對每一橫列sort
print(np.sort(e,axis=1))


import math
import numpy as np
"""
From tensorflow playground code
"""

def gen2Spiral(sample=500,noise=0,addSin=False):
    n = sample//2
    Xdata = []
    Ydata = []

    def genSpiral(deltaT,label):
        for i in range(n):
            r = float(i)/n*5.0
            t = 1.75*float(i)/n*2.0*math.pi + deltaT
            x = r*math.sin(t) #+ np.random.uniform(-1,1)*noise
            y = r * math.cos(t)  # + np.random.uniform(-1,1)*noise

            if addSin:
                Xdata.append([x, y,math.sin(x),math.sin(y)])
            else:
                Xdata.append([x, y])

            Ydata.append(label)

    genSpiral(0,1.0)
    genSpiral(math.pi,0.0)

    Xdata = np.array(Xdata)
    Ydata = np.array(Ydata)
    return Xdata,Ydata

# X,y=gen2Spiral(addSin=True)

# from matplotlib import pyplot as plt
# X,y=gen2Spiral()
# print(X)
# plt.scatter(X[:, 0], X[:, 1], marker='o', c=y,
#             s=25, edgecolor='k')
# plt.show()


import numpy as np

#******************
#Useful TOOLS
#******************

def spherical2cartesian(r = None, theta = None, phi = None):
    """
    Converts spherical coordinates to rectangular cartesian coordinates.

    Parameters
    ----------
    r : scalar or array_like, shape (n,)
       Radial distance from the origin of coordinates to the point in question.

    theta : scalar or array_like, shape (n,)
       Polar angle referred to the positive `z` - axis.

    phi : scalar or array_like, shape (n,)
       Azimuthal angle referred to the positive `x` - axis.

    Returns
    -------
    out : `numpy.ndarray`, shape (3,n)
       Array-like object with the resulting x,y,z transformation.

    See Also
    --------
    cartesian2spherical

    Notes
    -----
    Transformation equations:

    .. math:: x &= r\\sin(\\theta)\\cos(\\phi) \n
              y &= r\\sin(\\theta)\\sin(\\phi) \n
              z &= r\\cos(\\theta)

    """

    r = np.asarray(r)
    theta = np.asarray(theta)
    phi = np.asarray(phi)

    return np.array([
            r * np.sin(theta) * np.cos(phi),
            r * np.sin(theta) * np.sin(phi),
            r * np.cos(theta)
            ])

def cartesian2spherical(x=None, y=None, z=None):
    """
    Under development
    """
    return 0


from math import sqrt
import numpy as np

def read_KNN_dataFile(file):
    """
    A function to read data for KNN dataset provided.
    """
    A = np.genfromtxt(file)
    return A

def euclidean_distance(p1, p2):
    """
    Returns the Euclidean Distance of a particular point from rest of the points in dataset.
    """
    distance = 0
    for i in range(len(p1)-1):
        distance += (p1[i]-p2[i])**(2)
    return sqrt(distance)

def block_distance(p1, p2):
    """
    Returns the Block Distance of a particular point from rest of the points in dataset.
    """
    distance = 0
    for i in range(len(p1)-1):
        distance += abs(p1[i]-p2[i])
    return distance

def get_neighbours(train, test_row, num_neighbours, distance_metrics="block"):
    """
    Returns n nearest neighbours of a particular point in dataset based on euclidean or block distance.
    """
    distances = []
    for train_row in train:
        if distance_metrics=="block":
            distance = block_distance(test_row, train_row)
        else:
            distance = euclidean_distance(test_row, train_row)
        distances.append((train_row, distance))
    distances.sort(key=lambda tup: tup[1])
    neigbours = []
    for i in range(num_neighbours):
        neigbours.append(distances[i][0])
    return neigbours


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

# In[1]:

import numpy as np
import skimage.transform
import scipy
from scipy import ndimage
import matplotlib.pyplot as plt
from skimage.morphology import medial_axis, watershed

create_dist_map = lambda img, mask=None: medial_axis(img, mask, return_distance=True)[1]
import os
from skimage.measure import block_reduce

plt_settings = {"interpolation": "none"}

# # Distance Maps
# Here we calculate distance maps with the ```ndimage.distance_transform_``` family of functions. Initially we focus on test images since it is easier to see what is happening with these images.

# In[2]:

def generate_dot_image(size=100, cutoff=0.15):
    """
    Create a simple  synthetic image with a repeating pattern
    Keyword arguments:
    size -- the size of the image on one size, final size is size x size (default 100)
    imag -- the cutoff between 0 and 1, higher means less connected objects (default 0.15)
    """
    xx, yy = np.meshgrid(range(size), range(size))
    return (
        np.sin(6 * np.pi * xx / (100) - 1) + 1.25 * np.cos(5 * np.pi * yy / (100) - 2)
        > cutoff
    )

# In[3]:

img_bw = generate_dot_image(28, 0.50)
plt.imshow(img_bw, cmap="gray", **plt_settings)

# In[4]:

img_dist = ndimage.distance_transform_edt(img_bw)
plt.imshow(img_dist, **plt_settings)

# ## Comparing
# There are a number of different methods for ```distance_transform``` inside the ```ndimage``` package of ```scipy``` compare the results of the different approaches for this and other images.
# - What are the main differences?
# - Quantitatively (histogram) show what situations each one might be best suited for?

# In[5]:

# calculate new distance transforms
img_dist = ndimage.distance_transform_edt(img_bw)
img_dist_cityblock = ndimage.distance_transform_cdt(img_bw, metric="taxicab")
img_dist_chess = ndimage.distance_transform_cdt(img_bw, metric="chessboard")

fig, (ax1, ax2, ax3, ax4) = plt.subplots(1, 4, figsize=(30, 10))
ax1.imshow(img_bw, cmap="gray", **plt_settings)
ax1.set_title("Mask Image")
dmap_im = ax2.imshow(img_dist, vmax=img_dist.max(), **plt_settings)
ax2.set_title("Euclidean")
ax3.imshow(img_dist_cityblock, vmax=img_dist.max(), **plt_settings)
ax3.set_title("Cityblock")
ax4.imshow(img_dist_chess, vmax=img_dist.max(), **plt_settings)
ax4.set_title("Chess")
fig.subplots_adjust(right=0.8)
cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
cbar = fig.colorbar(dmap_im, cax=cbar_ax)
cbar_ax.set_title("Distance\n(px)")

# ## More Complicated Objects
# We now make the image bigger (changing the ```size``` parameter) and connect them together (the ```cutoff``` parameter)

# In[6]:

# use a bigger base image
img_bw = generate_dot_image(100, 0.15)
img_dist = ndimage.distance_transform_edt(img_bw)
img_dist_cityblock = ndimage.distance_transform_cdt(img_bw, metric="taxicab")
img_dist_chess = ndimage.distance_transform_cdt(img_bw, metric="chessboard")

fig, (ax1, ax2, ax3, ax4) = plt.subplots(1, 4, figsize=(30, 10))
ax1.imshow(img_bw, cmap="gray", **plt_settings)
ax1.set_title("Mask Image")
dmap_im = ax2.imshow(img_dist, vmax=img_dist.max(), **plt_settings)
ax2.set_title("Euclidean")
ax3.imshow(img_dist_cityblock, vmax=img_dist.max(), **plt_settings)
ax3.set_title("Cityblock")
ax4.imshow(img_dist_chess, vmax=img_dist.max(), **plt_settings)
ax4.set_title("Chess")
fig.subplots_adjust(right=0.8)
cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
cbar = fig.colorbar(dmap_im, cax=cbar_ax)
cbar_ax.set_title("Distance\n(px)")

# # Watershed
# We can use the watershed transform to segment closely connected objects. We see in the first image that the standard connected component labeling ```ndimage.label``` shows only 3 when we see 9

# In[7]:

cc_img = ndimage.label(img_bw)[0]

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(30, 10))
ax1.imshow(img_bw, cmap="gray", **plt_settings)
ax1.set_title("Mask Image")
dmap_im = ax2.imshow(cc_img, **plt_settings)
ax2.set_title("Connected Component Analysis")

# In[8]:

from skimage.feature import peak_local_max

def simple_watershed(img_dist, img_bw):
    """
    Calculate the watershed transform on an image and its distance map
    by finding the troughs and expanding from these points
    """
    local_maxi = peak_local_max(
        img_dist, labels=img_bw, footprint=np.ones((3, 3)), indices=False
    )
    markers = ndimage.label(local_maxi)[0]
    return watershed(-img_dist, markers, mask=img_bw)

# ## Applying Watershed
# We can apply watershed to the following image.
# - Why do the bottom row of objects not show up?
# - How can the results be improved

# In[9]:

ws_img = simple_watershed(img_dist, img_bw)

fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(30, 10))
ax1.imshow(img_bw, cmap="gray", **plt_settings)
ax1.set_title("Mask Image")
ax2.imshow(cc_img, **plt_settings)
ax2.set_title("Connected Component Analysis")
ax3.imshow(ws_img, **plt_settings)
ax3.set_title("Watershed Analysis")

# In[ ]:


from typing import Union, Optional, Dict
import numpy as np
from scipy import stats

def hist_entropy(
    X: np.ndarray,
    bins: Union[str, int] = "auto",
    correction: bool = True,
    hist_kwargs: Optional[Dict] = {},
) -> float:
    """Calculates the entropy using the histogram of a univariate dataset.
    Option to do a Miller Maddow correction.

    Parameters
    ----------
    X : np.ndarray, (n_samples)
        the univariate input dataset

    bins : {str, int}, default='auto'
        the number of bins to use for the histogram estimation

    correction : bool, default=True
        implements the Miller-Maddow correction for the histogram
        entropy estimation.

    hist_kwargs: Optional[Dict], default={}
        the histogram kwargs to be used when constructing the histogram
        See documention for more details:
        https://docs.scipy.org/doc/numpy/reference/generated/numpy.histogram.html

    Returns
    -------
    H_hist_entropy : float
        the entropy for this univariate histogram

    Example
    -------
    >> from scipy import stats
    >> from pysim.information import histogram_entropy
    >> X = stats.gamma(a=10).rvs(1_000, random_state=123)
    >> histogram_entropy(X)
    array(2.52771628)
    """
    # get histogram
    hist_counts = np.histogram(X, bins=bins, **hist_kwargs)

    # create random variable
    hist_dist = stats.rv_histogram(hist_counts)

    # calculate entropy
    H = hist_dist.entropy()

    # MLE Estimator with Miller-Maddow Correction
    if correction == True:
        H += 0.5 * (np.sum(hist_counts[0] > 0) - 1) / hist_counts[0].sum()

    return H


# -*- coding: utf-8 -*-
"""
Provides euclidean functions for working with TSP data.

One key function converts a numpy array of 2D coordinates into a matrix of euclidean distances
between them.
"""

import numpy as np

def gen_matrix(cities):
    """
    Creates a numpy array of euclidian distances between 2 sets of
    cities

    @points = numpy arrange of coordinate pairs

    """
    size = len(cities)
    matrix = np.zeros(shape=(size, size))

    row = 0
    col = 0

    for city1 in cities:
        col = 0
        for city2 in cities:
            matrix[row, col] = euclidean_distance(city1, city2)
            col+=1
        row +=1

    return matrix

def euclidean_distance(city1, city2):
    """
    Calculate euc distance between 2 cities
    5.5 ms to execute
    """
    return np.linalg.norm((city1-city2))

def euclidean_distance2(city1, city2):
    """
    An alterantive way to calculate euc distance
    between two cities

    6-7 ms to execute
    """
    v = np.square(city1 - city2)
    return np.sqrt(np.sum(v))


''' Python question by HackerRank
TODO 1:
You are given a 2-D array with dimensions NxM.
Your task is to perform the  tool over axis 0 and then find the Product of that result.
Input Format:
The first line of input contains space separated values of N and M.
The next N lines contains M space separated integers.
Output Format: Compute the sum along axis 0. Then, print the product of that sum.

TODO 2:
You are given a 2-D array with dimensions NxM.
Your task is to perform the min function over axis 1 and then find the max of that.
Input Format:
The first line of input contains the space separated values of N and M.
The next N lines contains M space separated integers.
Output Format: Compute the min along axis 1 and then print the max of that result.

TODO 3:
You are given a 2-D array of size NxM.
Your task is to find:
The mean along axis 1
The var along axis 0
The std along axis None

Input Format:
The first line contains the space separated values of N and M.
The next N lines contains M space separated integers.

Output Format
First, print the mean.
Second, print the var.
Third, print the std.

TODO 4:
You are given two arrays A and B. Both have dimensions of NxN.
Your task is to compute their matrix product.
Input Format:
The first line contains the integer N.
The next N lines contains  space separated integers of array A.
The following N lines contains N space separated integers of array B.
Output Format: Print the matrix multiplication of A and B.

TODO 5:
You are given two arrays: A and B.
Your task is to compute their inner and outer product.
Input Format
The first line contains the space separated elements of array .
The second line contains the space separated elements of array .
Output Format:
First, print the inner product.
Second, print the outer product.
'''

import numpy
import sys

def get_array_from_input():
    array_size = tuple([int(item) for item in input().split()])
    array_list = []
    for i in range(array_size[0]):
        array_list.append(tuple([int(item) for item in input().split()]))

    new_array = numpy.array(array_list)
    return new_array

#TODO 1: "Sum and Prod" (Numpy)
def sum_and_prod():
    # array_size = tuple([int(item) for item in input().split()])
    # array_list = []
    # for i in range(array_size[0]):
    #     array_list.append(tuple([int(item) for item in input().split()]))

    # my_array = numpy.array(array_list)
    my_array = get_array_from_input()
    step_1 = numpy.sum(my_array, axis = 0)
    print (step_1)
    print(numpy.prod(step_1))
    return 0

#TODO 2: "Min and Max" (Numpy)
def min_and_max():
    my_array = get_array_from_input()
    step_1 = numpy.min(my_array, axis=1)
    print(numpy.max(step_1))
    return 0

#TODO 3: "Mean, Var, and Std"
def mean_var_std():
    my_array = get_array_from_input()
    print(numpy.mean(my_array, axis=1))
    print(numpy.var(my_array, axis=0))
    print(numpy.std(my_array))
    return 0

#TODO 4: "Dot and Cross"
def dot_cross():
    array_size = int(input())
    array_list = []
    for i in range(array_size):
        array_list.append(tuple([int(item) for item in input().split()]))
    array_A = numpy.array(array_list)
    array_list.clear()
    for i in range(array_size):
        array_list.append(tuple([int(item) for item in input().split()]))
    array_B = numpy.array(array_list)

    print(numpy.dot(array_A, array_B))

    return 0

#TODO 5: "Inner_Outer"
def inner_outer():
    array_A = numpy.array([int(item) for item in input().split()])
    array_B = numpy.array([int(item) for item in input().split()])
    print(numpy.inner(array_A, array_B))
    print(numpy.outer(array_A, array_B))

def main():
    # sum_and_prod()
    # min_and_max()
    # mean_var_std()
    # dot_cross()
    inner_outer()

if __name__ == '__main__':
    main()


import numpy as np

def divisors(x: int):
    """
    Returns the divisors of x, up to the square root of x.
    """
    divs = []
    upperbound = int(np.round(np.sqrt(x)))

    for i in range(1, upperbound + 1):
        if x % i == 0:
            divs.append(i)
    return divs

def n_rows_cols(x: int, squared: bool=False):
    """
    Returns the 'squarest' divisors of x.

    The 'squarest' divisors of x are defined as the divisors of x that are
    closest together on the number line.

    Prime numbers are automatically assigned to the next largest square.

    :param x: The integer to split into rows and columns.
    :param squared: Whether to go for a squarer layout or not.
    """
    divs = divisors(x)
    isprime = len(divs) == 1 and divs[0] == 1
    if squared or isprime:
        nrows = int(np.ceil(np.sqrt(x)))
        ncols = nrows
    else:
        nrows = max(divisors(x))
        ncols = int(x / nrows)
    return nrows, ncols


import numpy
import pandas

def compute_cost(features, values, theta):
    """
    Compute the cost of a list of parameters, theta, given a list of features
    (input data points) and values (output data points).
    """
    m = len(values)
    sum_of_square_errors = numpy.square(numpy.dot(features, theta) - values).sum()
    cost = sum_of_square_errors / (2*m)

    return cost

def gradient_descent(features, values, theta, alpha, num_iterations):
    """
    Perform gradient descent given a data set with an arbitrary number of features.
    """

    # Write code here that performs num_iterations updates to the elements of theta.
    # times. Every time you compute the cost for a given list of thetas, append it
    # to cost_history.
    # See the Instructor notes for hints.

    cost_history = []
    m = len(values)

    ###########################
    ### YOUR CODE GOES HERE ###
    ###########################
    for i in range (0,num_iterations):
        predicted_value = numpy.dot(features,theta)
        theta = theta + alpha / m * numpy.dot(values-predicted_value,features)
        cost_history.append(compute_cost(features, values, theta))

    return theta, pandas.Series(cost_history) # leave this line for the grader


from typing import List

import numpy as np

def init_state(list_of_fish: List[int]) -> np.ndarray:
    # only store number of each counter value
    state = np.zeros(9, dtype=np.int64)  # int64 to avoid overflow star 2
    for fish in list_of_fish:
        state[fish] += 1
    return state

def simulate(state: np.ndarray, n_days: int)-> int:
    for _ in range(n_days):
        # spawning fish
        n_zeros = state[0]
        # decrement all counters
        state[0:8] = state[1:9]
        # spawn new fish
        state[8] = n_zeros
        # reset spawning fish
        state[6] += n_zeros
    return np.sum(state)

if __name__ == '__main__':
    def _main():
        with open('../inputs/day6.txt', 'r') as f:
            inp = f.readline().strip().split(',')
        inp = [int(s) for s in inp]

        print(f"Star 1: {simulate(init_state(inp), 80)}")
        print(f"Star 2: {simulate(init_state(inp), 256)}")

    _main()


# %%
import math
import copy
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from scipy.special import expit as sigmoid, logit
# %%
np.random.seed(0)
# %%

def generate_dataset(N_points):
    # 1 class
    radiuses = np.random.uniform(0, 0.5, size=N_points//2)
    angles = np.random.uniform(0, 2*math.pi, size=N_points//2)

    x_1 = np.multiply(radiuses, np.cos(angles)).reshape(N_points//2, 1)
    x_2 = np.multiply(radiuses, np.sin(angles)).reshape(N_points//2, 1)
    X_class_1 = np.concatenate((x_1, x_2), axis=1)
    Y_class_1 = np.full((N_points//2,), 1)

    # 0 class
    radiuses = np.random.uniform(0.6, 1, size=N_points//2)
    angles = np.random.uniform(0, 2*math.pi, size=N_points//2)

    x_1 = np.multiply(radiuses, np.cos(angles)).reshape(N_points//2, 1)
    x_2 = np.multiply(radiuses, np.sin(angles)).reshape(N_points//2, 1)
    X_class_0 = np.concatenate((x_1, x_2), axis=1)
    Y_class_0 = np.full((N_points//2,), 0)

    X = np.concatenate((X_class_1, X_class_0), axis=0)
    Y = np.concatenate((Y_class_1, Y_class_0), axis=0)
    return X, Y

N_points = 1000
X, Y = generate_dataset(N_points)

plt.scatter(X[:N_points//2, 0], X[:N_points//2, 1], color='red', label='class 1')
plt.scatter(X[N_points//2:, 0], X[N_points//2:, 1], color='blue', label='class 0')
plt.legend(loc=9, bbox_to_anchor=(0.5, -0.1), ncol=2)
plt.show()
# %%
X.shape, Y.shape
# %%
weights = dict(
    W1=np.random.randn(3, 2),
    b1=np.zeros(3),
    W2=np.random.randn(3),
    b2=0
)
# weights = {'W1': np.array([[-0.1049797,  1.36741498],
#                            [-1.65534404,  0.15364446],
#                            [-1.58447356,  0.84445431]]),
#            'b1': np.array([0., 0., 0.]),
#            'W2': np.array([-1.21286782,  0.28376955, -0.28219588]),
#            'b2': 0}
initial_weights = copy.deepcopy(weights)

# %%

def forward_propagation(X, weights):
    Z1 = X@weights['W1'].T + weights['b1']
    H = sigmoid(Z1)

    Z2 = H@weights['W2']+weights['b2']
    Y = sigmoid(Z2)

    return Y, Z2, H, Z1

# %%
forward_propagation(X, weights)

# %%

06Deep Learning/01Introduction to Neural Networks/OnlineClass/nn_from_scratch.py


import numpy as np
from pom.calculations import calculate_vertical_density_profile

def powspace(start, stop, power, num):
    start = np.power(start, 1/float(power))
    stop = np.power(stop, 1/float(power))
    return np.power(np.linspace(start, stop, num=num), power)

# ======================================================================================================================
# Test 1 - Constant profile
# Linear spacing with 61 points --> dz = 0.1
# One hour --> timestep = 60 s
# Constant temperature --> T = 20 C
# Constant salinity --> S = 35 psu

print("--------------------------------------------------------------")
print("Test 1")
print()

vertical_layers = 61
timestep = 60
vertical_spacing_staggered = np.linspace(0,6,vertical_layers)
temperature = 20 * np.ones(vertical_layers)
salinity = 35 * np.ones(vertical_layers)

vertical_density_profile = calculate_vertical_density_profile(temperature,salinity,vertical_spacing_staggered,timestep,vertical_layers)
density_profile_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    density_profile_spacing[i] = vertical_density_profile[i+1] - vertical_density_profile[i]
print("Test 1: vertical density profile = \n", vertical_density_profile)
print()
print("Test 1: density profile spacing = \n", density_profile_spacing)
print()

# Result: constant density profile
# ======================================================================================================================

# ======================================================================================================================
# Test 2 - Temperature dependancy
# Linear spacing with 61 points --> dz = 0.1
# One hour --> timestep = 60 s
# Linear increase in temperature --> 10 C < T < 20 C
# Constant salinity --> S = 35 psu

print("--------------------------------------------------------------")
print("Test 2")
print()

vertical_layers = 61
timestep = 60
vertical_spacing_staggered = np.linspace(0,6,vertical_layers)
temperature = np.linspace(10,20,vertical_layers)
temperature_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    temperature_spacing[i] = temperature[i+1] - temperature[i]
print("Test 2: temperature = \n", temperature)
print()
print("Test 2: temperature spacing = \n", temperature_spacing)
print()
salinity = 35 * np.ones(vertical_layers)

vertical_density_profile = calculate_vertical_density_profile(temperature,salinity,vertical_spacing_staggered,timestep,vertical_layers)
density_profile_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    density_profile_spacing[i] = vertical_density_profile[i+1] - vertical_density_profile[i]
print("Test 2: vertical density profile = \n", vertical_density_profile)
print()
print("Test 2: density profile spacing = \n", density_profile_spacing)
print()

# Result: linearly decreasing density with linearly increasing temperature
# Further testing: - lower values of temperature (potentially test negative to positive)
#                  - decreasing temperature profile (ocean temperature decreases with depth)
# ======================================================================================================================

# ======================================================================================================================
# Test 3 - Temperature dependancy
# Linear spacing with 61 points --> dz = 0.1
# One hour --> timestep = 60 s
# Logarithmic increase in temperature --> 10 C < T < 20 C
# Constant salinity --> S = 35 psu

print("--------------------------------------------------------------")
print("Test 3")
print()

vertical_layers = 61
timestep = 60
vertical_spacing_staggered = np.linspace(0,6,vertical_layers)
temperature = np.logspace(1,np.log(20)/np.log(10),vertical_layers)
temperature_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    temperature_spacing[i] = temperature[i+1] - temperature[i]
print("Test 3: temperature = \n", temperature)
print()
print("Test 3: temperature spacing = \n", temperature_spacing)
print()
salinity = 35 * np.ones(vertical_layers)

vertical_density_profile = calculate_vertical_density_profile(temperature,salinity,vertical_spacing_staggered,timestep,vertical_layers)
density_profile_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    density_profile_spacing[i] = vertical_density_profile[i+1] - vertical_density_profile[i]
print("Test 3: vertical density profile = \n", vertical_density_profile)
print()
print("Test 3: density profile spacing = \n", density_profile_spacing)
print()

# Result: decreasing density with increasing temperature
# ======================================================================================================================

# ======================================================================================================================
# Test 4 - Temperature dependancy
# Linear spacing with 61 points --> dz = 0.1
# One hour --> timestep = 60 s
# 10th power increase in temperature --> 10 C < T < 20 C
# Constant salinity --> S = 35 psu

print("--------------------------------------------------------------")
print("Test 4")
print()

vertical_layers = 61
timestep = 60
vertical_spacing_staggered = np.linspace(0,6,vertical_layers)
temperature = powspace(10,20,10,vertical_layers)
temperature_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    temperature_spacing[i] = temperature[i+1] - temperature[i]
print("Test 4: temperature = \n", temperature)
print()
print("Test 4: temperature spacing = \n", temperature_spacing)
print()
salinity = 35 * np.ones(vertical_layers)

vertical_density_profile = calculate_vertical_density_profile(temperature,salinity,vertical_spacing_staggered,timestep,vertical_layers)
density_profile_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    density_profile_spacing[i] = vertical_density_profile[i+1] - vertical_density_profile[i]
print("Test 4: vertical density profile = \n", vertical_density_profile)
print()
print("Test 4: density profile spacing = \n", density_profile_spacing)
print()

# Result: decreasing density with increasing temperature
# ======================================================================================================================

# ======================================================================================================================
# Test 5 - Temperature dependancy
# Linear spacing with 61 points --> dz = 0.1
# One hour --> timestep = 60 s
# Linear Decrease in temperature --> 10 C < T < 20 C
# Constant salinity --> S = 35 psu

print("--------------------------------------------------------------")
print("Test 5")
print()

vertical_layers = 61
timestep = 60
vertical_spacing_staggered = np.linspace(0,6,vertical_layers)
temperature = np.linspace(20,10,vertical_layers)
temperature_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    temperature_spacing[i] = temperature[i+1] - temperature[i]
print("Test 5: temperature = \n", temperature)
print()
print("Test 5: temperature spacing = \n", temperature_spacing)
print()
salinity = 35 * np.ones(vertical_layers)

vertical_density_profile = calculate_vertical_density_profile(temperature,salinity,vertical_spacing_staggered,timestep,vertical_layers)
density_profile_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    density_profile_spacing[i] = vertical_density_profile[i+1] - vertical_density_profile[i]
print("Test 5: vertical density profile = \n", vertical_density_profile)
print()
print("Test 5: density profile spacing = \n", density_profile_spacing)
print()

# Result: increasing density with decreasing temperature
# ======================================================================================================================

# ======================================================================================================================
# Test 6 - Salinity dependancy
# Linear spacing with 61 points --> dz = 0.1
# One hour --> timestep = 60 s
# Constant temperature --> T = 20 C
# Linearly increasing salinity --> 33 psu < S < 37 psu

print("--------------------------------------------------------------")
print("Test 6")
print()

vertical_layers = 61
timestep = 60
vertical_spacing_staggered = np.linspace(0,6,vertical_layers)
temperature = 20 * np.ones(vertical_layers)
salinity = np.linspace(33,37,vertical_layers)
salinity_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    salinity_spacing[i] = salinity[i+1] - salinity[i]
print("Test 6: salinity = \n", salinity)
print()
print("Test 6: salinity spacing = \n", salinity_spacing)
print()

vertical_density_profile = calculate_vertical_density_profile(temperature,salinity,vertical_spacing_staggered,timestep,vertical_layers)
density_profile_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    density_profile_spacing[i] = vertical_density_profile[i+1] - vertical_density_profile[i]
print("Test 6: vertical density profile = \n", vertical_density_profile)
print()
print("Test 6: density profile spacing = \n", density_profile_spacing)
print()

# Result: increasing density with increasing salinity
# ======================================================================================================================

# ======================================================================================================================
# Test 7 - Salinity dependancy
# Linear spacing with 61 points --> dz = 0.1
# One hour --> timestep = 60 s
# Constant temperature --> T = 20 C
# 10th power increase salinity --> 33 psu < S < 37 psu

print("--------------------------------------------------------------")
print("Test 7")
print()

vertical_layers = 61
timestep = 60
vertical_spacing_staggered = np.linspace(0,6,vertical_layers)
temperature = 20 * np.ones(vertical_layers)
salinity = powspace(33,37,10,vertical_layers)
salinity_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    salinity_spacing[i] = salinity[i+1] - salinity[i]
print("Test 7: salinity = \n", salinity)
print()
print("Test 7: salinity spacing = \n", salinity_spacing)
print()

vertical_density_profile = calculate_vertical_density_profile(temperature,salinity,vertical_spacing_staggered,timestep,vertical_layers)
density_profile_spacing = np.zeros(vertical_layers-1)
for i in range(0,vertical_layers-1):
    density_profile_spacing[i] = vertical_density_profile[i+1] - vertical_density_profile[i]
print("Test 7: vertical density profile = \n", vertical_density_profile)
print()
print("Test 7: density profile spacing = \n", density_profile_spacing)
print()

# Result: increasing density with increasing salinity
# ======================================================================================================================


""" Orbital Mechanics for Engineering Students Problem 1.23
Question:
Using an RK solver, solve the nonlinear Lorenz Equations:
    https://journals.ametsoc.org/jas/article/20/2/130/16956/Deterministic-Nonperiodic-Flow
    xDot = sigma(y-x)
    yDot = x(rho-z)-y
    zDot = xy - beta z
with:
    - sigma = 10
    - beta = 8/3
    - rho = 28
    - x = 0, y = 1, z = 0 at t = 0
Plot the trajectory x, y, z in three dimensions to see the Lorenz attractor.
Written by: J.X.J. Bannwarth
"""
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from orbitutils.solvers import rkf45

# Differential equations
def rates(t, Y, sigma=10., beta=8./3., rho=28.):
    F = np.zeros(Y.shape)
    F[0] = sigma*(Y[1]-Y[0])
    F[1] = Y[0]*(rho-Y[2]) - Y[1]
    F[2] = Y[0]*Y[1] - beta*Y[2]
    return F

# Title
print("Orbital Mechanics for Engineering Students Problem 1.23")

# Parameters
tSpan = np.array([0., 20.])
Y0 = np.array([0., 1., 0.])

# Solve numerically
y, t = rkf45(rates, Y0, tSpan)

# Plot answer
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.plot(y[:,0],y[:,1],y[:,2], label='$Y(t)$')
ax.scatter(Y0[0],Y0[1],Y0[2], color="red", label='$Y_0$')
ax.set_xlabel("$x$")
ax.set_ylabel("$y$")
ax.set_zlabel("$z$")
ax.set_title("Lorenz attractor")
ax.legend()
plt.show()


"""Module with activation function definitions."""

import numpy as np

def _identity(x: np.ndarray, derivative: bool = False) -> np.ndarray:
    """Identity activation function.

    Args:
        x: Input for function.
        derivative: Flag for calculating function derivative.

    Returns:
        Result.

    """
    if derivative:
        return np.ones_like(x)
    return x

def _relu(x: np.ndarray, derivative: bool = False) -> np.ndarray:
    """ReLu activation function.

    Args:
        x: Input for function.
        derivative: Flag for calculating function derivative.

    Returns:
        Result.

    """
    # pylint: disable=no-member
    if derivative:
        return np.heaviside(x, 0)
    return np.maximum(x, 0)

def _sigmoid(x: np.ndarray, derivative: bool = False) -> np.ndarray:
    """Sigmoid activation function.

    Args:
        x: Input for function.
        derivative: Flag for calculating function derivative.

    Returns:
        Result.

    """
    x = np.maximum(x, -100.0)
    sigmoid = 1 / (1 + np.exp(-x))
    if derivative:
        return sigmoid * (1 - sigmoid)
    return sigmoid

def _softplus(x: np.ndarray, derivative: bool = False) -> np.ndarray:
    """Softplus activation function.

    Args:
        x: Input for function.
        derivative: Flag for calculating function derivative.

    Returns:
        Result.

    """
    x = np.maximum(x, -100.0)
    if derivative:
        return 1 / (1 + np.exp(-x))
    return x + np.log(1 + np.exp(-x))

def _tanh(x: np.ndarray, derivative: bool = False) -> np.ndarray:
    """Hyperbolic tangent activation function.

    Args:
        x: Input for function.
        derivative: Flag for calculating function derivative.

    Returns:
        Result.

    """
    tanh = np.tanh(x)
    if derivative:
        return 1 - tanh * tanh
    return tanh

_ACTIVATION_DICT = {
    'identity': _identity,
    'relu': _relu,
    'sigmoid': _sigmoid,
    'softplus': _softplus,
    'tanh': _tanh}


# -*- coding: utf-8 -*-
"""
Sistemi lineari
"""
import numpy as np
def solveL(L,b):
    # L matrice triangolare inferiore
    m,n = L.shape
    if m != n:
        print("matrice non quadrata")
        return [],1
    # Test singolarita, se L e triangolare non singolare
    # Nella diagonale tutti gli elementi sono non nulli
    # --------------------------------------------------
    # np all verifica che tutti gli elementi lungo un asse siano "True",
    # diag restituisce un array [...] like
    if np.all(np.diag(L)) != True:
        print("elemento diagonale nullo")
        return[],1
    x = np.zeros((n,1))
    for i in range(n):
        sommatoria = np.dot(L[i,:i],x[:i])
        x[i] = (b[i] - sommatoria)/L[i,i]
    return x,0

def solveU(U,b):
    # U matrice triangolare superiore
    m,n = U.shape
    if m != n:
        print("matrice non quadrata")
        return [],1
    if np.all(np.diag(U)) != True:
        print("elemento diagonale nullo")
        return[],1
    x = np.zeros((n,1))
    for i in reversed(range(n)):
        sommatoria = np.dot(U[i,i+1:n], x[i+1:n])
        x[i] = (b[i] - sommatoria)/U[i:i]
    return x,0

# scambia la k-esima riga con la p-esima
def SwapRows(M, k, p):
    M[[k,p],:] = M[[p,k],:]

def fattorizzazioneLU(A, pivoting = 0):
    m,n = A.shape
    if m != n:
        print("Matrice non quadrata:")
        return [],[],[],1 # P,L,U,flag
    P = np.eye(n)
    U = A.copy()
    for k in range(n-1):
        # Test su elemento pivotale
        if U[k,k] == 0:
            print("Elemento pivotale nullo")
            return [],[],[],1
        p = np.argmax(abs(U[k,k])) + k
        if pivoting and p != k:
            SwapRows(U,k,p)
            SwapRows(P,k,p)
        # Eliminazione gaussiana
        U[k+1:n,k] = U[k+1:n,k]/U[k,k] # Memorizzo i moltiplicatori della k-esima colonna
        U[k+1:n,k+1:n] = U[k+1:n,k+1:n] - np.outer(U[k+1:n,k],U[k,k+1:n]) # El. gaussiana sulla matrice
    L = np.tril(U,-1) + np.eye(n)
    U = np.triu(U) # estrae la triangolare superiore, con diagonale
    return P,L,U,0

def solveLU(P,L,U,b):
    y,flag = solveL(L,np.dot(P,b))
    if flag:
        return [],1
    return solveU(U,y)


"""
File: interpolate_exp_cos.py
Copyright (c) 2016 Andrew Malfavon
License: MIT
Exercise B.2
Description: Interpolate a discrete function.
"""

import numpy as np
import matplotlib.pyplot as plt

#plots the given function for eps>0 and n nodes.
def plot(eps, n):
    x = np.linspace(0, 1, n + 1)
    y = np.sin(1 / (x + (eps)))
    plt.plot(x, y)
    plt.axis([-0.2, 1.1, -1.1, 1.1])
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title('f(x)')

#plots for n and n+10
def plot_refined(eps, n1):
    n2 = n1 + 10
    x1 = np.linspace(0, 1, n1 + 1)
    y1 = np.sin(1 / (x1 + (eps)))
    x2 = np.linspace(0, 1, n2 + 1)
    y2 = np.sin(1 / (x2 + (eps)))
    plt.plot(x1, y1)
    plt.plot(x2, y2)
    plt.axis([-0.2, 1.1, -1.1, 1.1])
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title('f(x)')

#Find how large n should be for the difference between the two functions to be less than 0.1
def find_large_n(eps):
    n1 = 1
    n2 = n1 + 10
    x1 = np.linspace(0, 1, n1 +1)
    x2 = np.linspace(0, 1, n2 + 1)
    f1 = np.sin(1 / (x1 + (eps)))
    f2 = np.sin(1 / (x2 + (eps)))
    while (abs(max(f2) - max(f1)) >= 0.1):
        n1 += 1
        n2 += 1
        x1 = np.linspace(0, 1, n1 +1)
        x2 = np.linspace(0, 1, n2 + 1)
        f1 = np.sin(1 / (x1 + (eps)))
        f2 = np.sin(1 / (x2 + (eps)))
    return n1

#test that for each eps the difference of the max values of the functions is less than 0.1
def test():
    eps_array = [0.1, 0.2, 0.05]#each eps value to be tested
    difference_array = []
    for elem in eps_array:#use the n1 calculated in find_large_n
        n1 = find_large_n(elem)
        n2 = n1 + 10
        x1 = np.linspace(0, 1, n1 +1)
        x2 = np.linspace(0, 1, n2 + 1)
        f1 = np.sin(1 / (x1 + (elem)))
        f2 = np.sin(1 / (x2 + (elem)))
        difference_array.append(abs(max(f1) - max(f2)))#make an array of each difference
    for i in range(len(difference_array)):
        assert difference_array[i] < 0.1#check the resulting difference is less than 0.1


# -*- encoding: utf-8 -*-

import numpy as np

class PCA(object):
    """Dimension Reduction using Principal Component Analysis (PCA)

    It is the procces of computing principal components which explains the
    maximum variation of the dataset using fewer components.

    :type  n_components: int, optional
    :param n_components: Number of components to consider, if not set then
                         `n_components = min(n_samples, n_features)`, where
                         `n_samples` is the number of samples, and
                         `n_features` is the number of features (i.e.,
                         dimension of the dataset).

    :Attributes:
    :type  covariance_: np.ndarray
    :param covariance_: Coviarance Matrix

    :type  eig_vals_: np.ndarray
    :param eig_vals_: Calculated Eigen Values

    :type  eig_vecs_: np.ndarray
    :param eig_vecs_: Calculated Eigen Vectors

    :type  explained_variance_: np.ndarray
    :param explained_variance_: Explained Variance of Each Principal Components

    :type  cum_explained_variance_: np.ndarray
    :param cum_explained_variance_: Cumulative Explained Variables
    """

    def __init__(self, n_components : int = None):
        """Default Constructor for Initialization"""

        self.n_components = n_components

    def fit_transform(self, X : np.ndarray):
        """Fit the PCA algorithm into the Dataset"""

        if not self.n_components:
            self.n_components = min(X.shape)

        self.covariance_ = np.cov(X.T)

        # calculate eigens
        self.eig_vals_, self.eig_vecs_ = np.linalg.eig(self.covariance_)
        # self.eig_pairs_ = [(np.abs(self.eig_vals_[i]), self.eig_vecs_[:, i]) for i in range(len(self.eig_vals_))]

        # explained variance
        _tot_eig_vals = sum(self.eig_vals_)
        self.explained_variance_ = np.array([(i / _tot_eig_vals) * 100 for i in sorted(self.eig_vals_, reverse = True)])
        self.cum_explained_variance_ = np.cumsum(self.explained_variance_)

        # define `W` as `d x k`-dimension
        self.W_ = self.eig_vecs_[:, :self.n_components]

        print(X.shape, self.W_.shape)
        return X.dot(self.W_)


#Write the representation of ℜ2 as a list comprehension - use ranges between -10 and 10 for all values of x and y.
#Write the representation of ℜ3 as a list comprehension - use ranges between -10 and 10 for all values of x, y, and z.
#Write a list comprehension that represents the the set V = {(x, y, z) | x, y, z ∈ ℜ and x+y = 11}. Use ranges between -10 and 10 for all values of x, y, and z.
#Choose three values of x, y, and z that show the set V = {(x, y, z) | x, y, z ∈ ℜ and x+y = 11} is not a subspace of ℜ3. These values should represent a tuple that would be in vector V had it been a vector subspace. Each value should also be between -10 and 10.

import numpy as np
R2 = [[x, y] for x in range(-10, 10) for y in range(-10, 10)]
R3 = [[x,y,z] for x in range(-10, 10) for y in range(-10,10) for z in range(-10, 10)]
V = [[x,y,z] for x in range(-10, 10) for y in range(-10,10) for z in range(-10, 10) if x + y == 11]
not_sub = [[x,y,z] for x in range(-10, 10) for y in range(-10,10) for z in range(-10, 10) if x + y != 11]


"""
Implementing second moments of area from

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

"""
import numpy as np

class SecondMoment():

    def __init__(self):
        return

    @staticmethod
    def RectArea(b, h):
        """
                /\ y
                |
             b  |
            ---------
            |       |
            |       |
            |       |  h --->x
            |       |
            ---------
        :param b: width
        :param h: heigth
        :return: The second moments of area [Ixx, Iyy]
        """

        return [ (b*h**3 )/12, (b**3*h)/12 ]

    @staticmethod
    def PolarRectArea(b, h):
        """
             /\ y
                |
             b  |
            ---------
            |       |
            |       |
            |       |  h --->x
            |       |
            ---------

        :param b:
        :param h:
        :return:

        """
        # from  https://www.engineersedge.com/polar-moment-inertia.htm
        return (b * h*(b**2 + h**2))/12

    @staticmethod
    def CircularArea(r):
        """

        :param r: radius
        :return:
        """

        return [(np.pi * r**4)/4 , (np.pi * r**4) / 4]

    @staticmethod
    def PolarCircularArea(r):
        """
        https://www.fxsolver.com/browse/formulas/Polar+Moment+of+Area+-+Circular+Cross+Section
        :param r:  radius
        :return:
        """
        return (np.pi * r ** 4) / 2


import numpy as np
import matplotlib.pyplot as plt
import time
import tqdm
import copy

from pre_process import load_mat, remove_mean, separate_data, compute_eigenvalues_eigenvectors

def sort_eigenvalues_eigenvectors(eigenvalues, eigenvectors):
    p = np.argsort(np.abs(eigenvalues))[::-1]
    eigenvalues_ = copy.deepcopy(eigenvalues[p])
    eigenvectors_ = copy.deepcopy(np.real(eigenvectors[:,p]))
    return eigenvalues_, eigenvectors_

def non_zero(eigenvalues,lim=0.001):
    count = 0
    print(eigenvalues.shape)
    for i in eigenvalues:
        if i<lim:
            count+=1
    return count

def compare(a1, a2, lim=0.001):
    N = a1.shape[0] if a1.shape[0] < a2.shape[0] else a2.shape[0]
    for i in range(N):
        if np.abs(a1[i]-a2[i])>lim:
            return False
    return True

def main():
    # Load the data from the matrix
    X, Y = load_mat("data/face.mat")
    dataset = separate_data((X,Y))
    X_train = dataset[0][0]

    # Compute and remove mean from the training dataset
    mean = X_train.mean(axis=1).reshape(-1,1)

    A = X_train - mean

    #Display the mean face for verification
    plt.figure()
    plt.title('Mean Face')
    mean = mean.reshape((46,56))
    mean = np.rot90(mean,3)
    plt.imshow(mean, cmap="gray")
    plt.savefig("results/q1/mean.png", format="png", transparent=True) # TODO: add eigenface also

    D, N = X_train.shape
    start = time.time()
    S_naive = copy.deepcopy((1 / N) * np.dot(A, A.T))
    end = time.time()
    print("Calculated S inefficiently, took: {} s".format(end-start))

    start = time.time()
    S_efficient = copy.deepcopy((1/N) * np.dot(A.T, A))
    end = time.time()
    print("Calculated S efficiently, took: {} s".format(end-start))

    start = time.time()
    _l_naive,_u_naive = np.linalg.eig(S_naive)
    end = time.time()
    print("Calculated eigenvectors, eigenvalues efficiently, took: {} s".format(end-start))

    start = time.time()
    _l_efficient,_u_efficient = np.linalg.eig(S_efficient)
    end = time.time()
    print("Calculated eigenvectors, eigenvalues inefficiently, took: {} s".format(end-start))

    _u_efficient = copy.deepcopy(np.matmul(A,_u_efficient))
    _u_efficient = _u_efficient / np.linalg.norm(_u_efficient,axis=0)
    indexes = np.argsort(np.abs(_l_naive))[::-1]
    u_naive = np.real(_u_naive[:, indexes])
    l_naive = _l_naive[indexes]

    _indexes = np.argsort(np.abs(_l_efficient))[::-1]
    u_efficient = np.real(_u_efficient[:, _indexes])
    l_efficient = _l_efficient[_indexes]

    print("Non-zero eigenvalues inefficiently {}".format(non_zero(l_naive)))
    print("Non-zero eigenvalues efficiently {}".format(non_zero(l_efficient)))

    print("Are eigenvalues for both the same? {}".format(compare(l_efficient, l_naive)))
    for i in range(u_efficient.shape[1]):
        print(u_efficient[:,i], u_naive[:,i])
        print("Are eigenvectors for both the same? {}".format(compare(u_efficient[:,i], u_naive[:,i])))

    l_naive_sum = np.sum(l_naive)
    k = [0] * N

    for i in range(N):
        for j in range(i):
            k[i]+=l_naive[j]
        k[i]/=l_naive_sum
    k = np.array(k)
    print("Relative accurracy for M=10: {}, M=50: {}, M = 100: {}, M=200: {}".format(k[10], k[50], k[100], k[200]))

    plt.figure()
    plt.title('Relative Reconstruction Error')
    plt.xlabel('$\lambda_{i}$ eigenvalue sum')
    plt.ylabel('Accuracy')
    plt.plot(k)
    plt.savefig('results/q1/relative_accuracy.png',
                format='png', transparent=True)

    plt.figure()
    plt.title('Efficient Eigenvalues')
    plt.xlabel('$l_{m}$ eigenvalue')
    plt.ylabel('Real Value')
    plt.plot(l_efficient)
    plt.savefig('results/q1/eigenvalues_efficient.png',
                format='png', transparent=True)

    plt.figure()
    plt.title('Naive Eigenvalues')
    plt.xlabel('$l_{m}$ eigenvalue')
    plt.ylabel('Real Value')
    plt.plot(l_naive)
    plt.savefig('results/q1/eigenvalues_naive.png',
                format='png', transparent=True)

    plt.figure()
    plt.title('Combined Efficient and Naive Eigenvalues')
    plt.xlabel('$l_{m}$ eigenvalue')
    plt.ylabel('Real Value')
    plt.plot(l_naive, label="Naive implementation")
    plt.plot(l_efficient, label="Efficient implementation")
    plt.legend()
    plt.savefig('results/q1/eigenvalues_efficient_naive.png',
                format='png', transparent=True)

    f, axarr = plt.subplots(2,3)
    for i in range(3):
        img = np.real(u_naive[:,i].reshape((46,56)))
        img = np.rot90(img,3)
        axarr[0,i].imshow(img, cmap=plt.cm.Greys)
        axarr[0,i].set_title("Eigenface %d" %i)
    for i in range(3):
        img = np.real(u_efficient[:,i].reshape((46,56)))
        img = np.rot90(img,3)
        axarr[1,i].imshow(img, cmap=plt.cm.Greys)

    plt.savefig("results/q1/eigenfaces.png", format="png", transparent=True)

    # Plot mean and eigenface together

    plt.close()

if __name__ == '__main__':
    main()


import numpy as np
import matplotlib.pyplot as plt

def read_csv_file(file):
    # Read data from CSV file
    data = np.genfromtxt(file, delimiter=',', dtype=np.float, skip_header=True)
    return data

def mse(y_prediction, y_true):
    # Calculate mean square error (mse)
    return np.average((y_true - y_prediction) ** 2)

def rmse(y_prediction, y_true):
    # Calculate root mean square error (mse)
    return np.sqrt(mse(y_prediction, y_true))

def train_test_split(x, y, train_size=0.8):
    # Split data into training and test set
    train_size = int(len(x) * train_size)

    x_train = x[:train_size]
    y_train = y[:train_size]

    x_test = x[train_size:]
    y_test = y[train_size:]

    return x_train, x_test, y_train, y_test

def normal_equation(x, y):
    # Linear regression via normal equation
    return np.matmul(np.matmul(np.linalg.inv(np.matmul(x.T, x)), x.T), y)

def gradient_descent(x, y, learning_rate=0.001, nr_of_epochs=10000):
    # Linear regression via gradient descent
    weights = np.zeros(x.shape[1])
    error_mse = []
    error_rmse = []

    for i in range(nr_of_epochs):

        # Calculate output (= prediction) of activation function
        prediction = predict(x, weights)

        # Calculate error between prediction and output variable
        errors = (y - prediction)

        # Calculate gradient descent to update weights
        gradient = np.dot(x.T, errors)
        weights += learning_rate * gradient

        error_mse.append(mse(prediction, y))
        error_rmse.append(rmse(prediction, y))

    return weights, error_mse, error_rmse

def predict(x, weights):
    # Predict output (i.e. MEDV) based on given features (x) and weights
    return np.dot(x, weights)

def print_subplot(axs, error_rmse, rmse_normal_equation, learning_rate):
    axs.plot(error_rmse)
    axs.set_title('Learning rate {:0.5f}'.format(learning_rate))
    axs.set_xlabel('Nr. of Epochs')
    axs.set_ylabel('RMSE')
    axs.axhline(rmse_normal_equation, color='red', linestyle='dotted')
    axs.set_ylim([0, 30])

def main():

    boston_housing_data = read_csv_file('mass_boston.csv')

    # Change printing precision of matrices
    np.set_printoptions(precision=3)

    # Remove entries where MEDV is equal to 50.0, as this variable has been censored for higher values
    boston_housing_data = boston_housing_data[boston_housing_data[:, 13] != 50.0]

    # Split features and output variable (y = median house value)
    features = boston_housing_data[:, 0:13]
    y = boston_housing_data[:, 13]

    # Normalise features by min-max
    min = features.min(axis=0)
    max = features.max(axis=0)
    norm_features = (features - min) / (max - min)

    # Covariance matrix to find features with highest covariance with output variable
    cov = np.corrcoef(boston_housing_data.T)

    plt.matshow(cov, cmap='seismic')
    plt.xticks(np.arange(14))
    plt.yticks(np.arange(14))
    plt.show()

    print("Correlation with MEDV {}\n".format(cov[13]))

    # Select features with highest covariance (2 = INDUS, 4 = NOX, 5 = RM, 9 = TAX, 10 = PTRATIO, 12 = LSTAT)
    selected_features = norm_features[:, [2, 4, 5, 9, 10, 12]]

    # Add bias
    x = np.c_[np.ones((len(selected_features))), selected_features]

    # Split data into train and test
    x_train, x_test, y_train, y_test = train_test_split(x, y, train_size=0.8)

    print("Linear regression via normal equation")

    weights = normal_equation(x_train, y_train)
    predictions = predict(x_train, weights)

    print("Weights {}".format(weights))
    rmse_normal_equation = rmse(predictions, y_train)

    print("Train: MSE {:2.4f} / RMSE {:1.4f}".format(mse(predictions, y_train), rmse(predictions, y_train)))

    predictions = predict(x_test, weights)

    print("Test: MSE {:2.4f} / RMSE {:1.4f}\n".format(mse(predictions, y_test), rmse(predictions, y_test)))

    fig, axs = plt.subplots(2)
    plt.subplots_adjust(hspace=0.6)

    # Linear regression via gradient descent with optimal learning rate
    nr_of_epochs = 1000
    learning_rate = 0.001

    print("Linear regression via gradient descent (learning rate {:0.5f})".format(learning_rate))

    weights, error_mse, error_rmse = gradient_descent(x_train, y_train, learning_rate, nr_of_epochs)
    predictions = predict(x_train, weights)

    print("Weights {}".format(weights))

    print("Train: MSE {:2.4f} / RMSE {:1.4f}".format(mse(predictions, y_train), rmse(predictions, y_train)))

    predictions = predict(x_test, weights)

    print("Test: MSE {:2.4f} / RMSE {:1.4f}\n".format(mse(predictions, y_test), rmse(predictions, y_test)))

    print_subplot(axs[0], error_rmse, rmse_normal_equation, learning_rate)

    # Linear regression via gradient descent with small learning rate
    learning_rate = 0.00001

    print("Linear regression via gradient descent (learning rate {:0.5f})".format(learning_rate))

    weights, error_mse, error_rmse = gradient_descent(x_train, y_train, learning_rate, nr_of_epochs)
    predictions = predict(x_train, weights)

    print("Weights {}".format(weights))

    print("Train: MSE {:2.4f} / RMSE {:1.4f}".format(mse(predictions, y_train), rmse(predictions, y_train)))

    predictions = predict(x_test, weights)

    print("Test: MSE {:2.4f} / RMSE {:1.4f}\n".format(mse(predictions, y_test), rmse(predictions, y_test)))

    print_subplot(axs[1], error_rmse, rmse_normal_equation, learning_rate)

    plt.show()

if __name__ == '__main__':
    main()


# 1.Write a NumPy program to get the numpy version and show numpy build configuration.
import numpy as np

print(np.__version__)
print(np.show_config())
# 2. Write a NumPy program to  get help on the add function.
import numpy as np

print(np.info(np.add))
# 3. Write a NumPy program to test whether none of the elements of a given array is zero.
import numpy as np

x = np.array([1, 2, 3, 4])
print("Original array:")
print(x)
print("Test if none of the elements of the said array is zero:")
print(np.all(x))
x = np.array([0, 1, 2, 3])
print("Original array:")
print(x)
print("Test if none of the elements of the said array is zero:")
print(np.all(x))
# 4. Write a NumPy program to test whether any of the elements of a given array is non-zero.
import numpy as np

x = np.array([1, 0, 0, 0])
print("Original array:")
print(x)
print("Test whether any of the elements of a given array is non-zero:")
print(np.any(x))
x = np.array([0, 0, 0, 0])
print("Original array:")
print(x)
print("Test whether any of the elements of a given array is non-zero:")
print(np.any(x))
# 5. Write a NumPy program to test a given array element-wise for finiteness (not infinity or not a Number).
import numpy as np

a = np.array([1, 0, np.nan, np.inf])
print("Original array")
print(a)
print("Test a given array element-wise for finiteness :")
print(np.isfinite(a))
# 6. Write a NumPy program to test element-wise for positive or negative infinity.
import numpy as np

a = np.array([1, 0, np.nan, np.inf])
print("Original array")
print(a)
print("Test element-wise for positive or negative infinity:")
print(np.isinf(a))
# 7. Write a NumPy program to test element-wise for NaN of a given array.
import numpy as np

a = np.array([1, 0, np.nan, np.inf])
print("Original array")
print(a)
print("Test element-wise for NaN:")
print(np.isnan(a))
# 8. Write a NumPy program to test element-wise for complex number, real number of a given array. Also test whether a given number is a scalar type or not.
import numpy as np

a = np.array([1 + 1j, 1 + 0j, 4.5, 3, 2, 2j])
print("Original array")
print(a)
print("Checking for complex number:")
print(np.iscomplex(a))
print("Checking for real number:")
print(np.isreal(a))
print("Checking for scalar type:")
print(np.isscalar(3.1))
print(np.isscalar([3.1]))
# 9. Write a NumPy program to test whether two arrays are element-wise equal within a tolerance.
import numpy as np

print("Test if two arrays are element-wise equal within a tolerance:")
print(np.allclose([1e10, 1e-7], [1.00001e10, 1e-8]))
print(np.allclose([1e10, 1e-8], [1.00001e10, 1e-9]))
print(np.allclose([1e10, 1e-8], [1.0001e10, 1e-9]))
print(np.allclose([1.0, np.nan], [1.0, np.nan]))
print(np.allclose([1.0, np.nan], [1.0, np.nan], equal_nan=True))
# 10. Write a NumPy program to create an element-wise comparison (greater, greater_equal, less and less_equal) of two given arrays
import numpy as np

x = np.array([3, 5])
y = np.array([2, 5])
print("Original numbers:")
print(x)
print(y)
print("Comparison - greater")
print(np.greater(x, y))
print("Comparison - greater_equal")
print(np.greater_equal(x, y))
print("Comparison - less")
print(np.less(x, y))
print("Comparison - less_equal")
print(np.less_equal(x, y))
# 11. Write a NumPy program to create an element-wise comparison (equal, equal within a tolerance) of two given arrays
import numpy as np

x = np.array([72, 79, 85, 90, 150, -135, 120, -10, 60, 100])
y = np.array([72, 79, 85, 90, 150, -135, 120, -10, 60, 100.000001])
print("Original numbers:")
print(x)
print(y)
print("Comparison - equal:")
print(np.equal(x, y))
print("Comparison - equal within a tolerance:")
print(np.allclose(x, y))
# 12. Write a NumPy program to create an array with the values 1, 7, 13, 105 and determine the size of the memory occupied by the array
import numpy as np

X = np.array([1, 7, 13, 105])
print("Original array:")
print(X)
print("Size of the memory occupied by the said array:")
print("%d bytes" % (X.size * X.itemsize))
# 13. Write a NumPy program to create an array of 10 zeros,10 ones, 10 fives.
import numpy as np

array = np.zeros(10)
print("An array of 10 zeros:")
print(array)
array = np.ones(10)
print("An array of 10 ones:")
print(array)
array = np.ones(10) * 5
print("An array of 10 fives:")
print(array)
# 14. Write a NumPy program to create an array of the integers from 30 to70.
import numpy as np

array = np.arange(30, 71)
print("Array of the integers from 30 to70")
print(array)
# 15. Write a NumPy program to create an array of all the even integers from 30 to 70.
import numpy as np

array = np.arange(30, 71, 2)
print("Array of all the even integers from 30 to 70")
print(array)
# 16. Write a NumPy program to create a 3x3 identity matrix.
import numpy as np

array_2D = np.identity(3)
print('3x3 matrix:')
print(array_2D)
# 17. Write a NumPy program to generate a random number between 0 and 1.
import numpy as np

rand_num = np.random.normal(0, 1, 1)
print("Random number between 0 and 1:")
print(rand_num)
# 18. Write a NumPy program to generate an array of 15 random numbers from a standard normal distribution.
import numpy as np

rand_num = np.random.normal(0, 1, 15)
print("15 random numbers from a standard normal distribution:")
print(rand_num)
# 19. Write a NumPy program to create a vector with values ranging from 15 to 55 and print all values except the first and last.
import numpy as np

v = np.arange(15, 55)
print("Original vector:")
print(v)
print("All values except the first and last of the said vector:")
print(v[1:-1])
# 20. Write a NumPy program to create a 3X4 array using and iterate over it.
import numpy as np

a = np.arange(10, 22).reshape((3, 4))
print("Original array:")
print(a)
print("Each element of the array is:")
for x in np.nditer(a):
    print(x, end=" ")
# 20. Write a NumPy program to create a 3X4 array using and iterate over it.
import numpy as np

a = np.arange(10, 22).reshape((3, 4))
print("Original array:")
print(a)
print("Each element of the array is:")
for x in np.nditer(a):
    print(x, end=" ")


# https://www.hackerrank.com/challenges/np-array-mathematics/problem

# Inputs
standard_input = """2 4
1 2 3 4
1 2 3 4
5 6 7 7
5 6 7 7"""

# no [use-before-def]
m = None

import numpy

m, n = [int(s) for s in input().split()]
# 2 4

a = numpy.array([input().split() for _ in range(m)], int)
# 1 2 3 4
# 1 2 3 4

b = numpy.array([input().split() for _ in range(m)], int)
# 5 6 7 7
# 5 6 7 7

print(a + b)
print(a - b)
print(a * b)
print(a // b)
print(a % b)
print(a ** b)
# [[ 6  8 10 11]
#  [ 6  8 10 11]]
# [[-4 -4 -4 -3]
#  [-4 -4 -4 -3]]
# [[ 5 12 21 28]
#  [ 5 12 21 28]]
# [[0 0 0 0]
#  [0 0 0 0]]
# [[1 2 3 4]
#  [1 2 3 4]]
# [[    1    64  2187 16384]
#  [    1    64  2187 16384]]

""" Reference

a = numpy.array([1, 2, 3, 4], float)
b = numpy.array([5, 6, 7, 8], float)

print(a + b)
print(numpy.add(a, b))
# [ 6.  8. 10. 12.]
# [ 6.  8. 10. 12.]

print(a - b)
print(numpy.subtract(a, b))
# [-4. -4. -4. -4.]
# [-4. -4. -4. -4.]

print(a * b)
print(numpy.multiply(a, b))
# [ 5. 12. 21. 32.]
# [ 5. 12. 21. 32.]

print(a / b)
print(numpy.divide(a, b))
# [0.2        0.33333333 0.42857143 0.5       ]
# [0.2        0.33333333 0.42857143 0.5       ]

print(a % b)
print(numpy.mod(a, b))
# [1. 2. 3. 4.]
# [1. 2. 3. 4.]

print(a ** b)
print(numpy.power(a, b))
# [1.0000e+00 6.4000e+01 2.1870e+03 6.5536e+04]
# [1.0000e+00 6.4000e+01 2.1870e+03 6.5536e+04]

"""

__Training__/Python - HackerRank/16. Numpy/Array Mathematics.py


import numpy as np
import scipy
import scipy.spatial.distance as sd

from UQpy.utilities.ValidationTypes import RandomStateType, Numpy2DFloatArray
from UQpy.utilities.kernels import EuclideanKernel
from scipy.spatial.distance import pdist

class GaussianKernel(EuclideanKernel):
    """
    A class to calculate the Gaussian kernel defined as:

    .. math::
        k(x_j, x_i) = \exp[-(x_j - xj)^2/4\epsilon]

    """
    def __init__(self, epsilon: float = 1.0):
        """
        :param epsilon: Scale parameter of the Gaussian kernel
        """
        super().__init__()
        self.epsilon = epsilon

    def kernel_entry(self, xi: Numpy2DFloatArray, xj: Numpy2DFloatArray):
        """
        Given two points, this method computes the Gaussian kernel value between those two points

        :param xi: First point.
        :param xj: Second point.
        :return: Float representing the kernel entry.
        """
        if len(xi.shape) == 1:
            d = pdist(np.array([xi, xj]), "sqeuclidean")
        else:
            d = np.linalg.norm(xi-xj, 'fro') ** 2
        return np.exp(-d / (2*self.epsilon**2))

    def optimize_parameters(self, data: np.ndarray, tolerance: float,
                            n_nearest_neighbors: int,
                            n_cutoff_samples: int,
                            random_state: RandomStateType = None):
        """

        :param data: Set of data points.
        :param tolerance: Tolerance below which the Gaussian kernel is assumed to be zero.
        :param n_nearest_neighbors: Number of neighbors to use for cut-off estimation.
        :param n_cutoff_samples: Number of samples to use for cut-off estimation.
        :param random_state: Random seed used to initialize the pseudo-random number generator. If an :any:`int` is
         provided, this sets the seed for an object of :class:`numpy.random.RandomState`. Otherwise, the
         object itself can be passed directly.
        """

        cut_off = self._estimate_cut_off(data, n_nearest_neighbors, n_cutoff_samples, random_state)
        self.epsilon = cut_off ** 2 / (-np.log(tolerance))

    def _estimate_cut_off(self, data, n_nearest_neighbors, n_partition, random_state):
        data = np.atleast_2d(data)
        n_points = data.shape[0]
        if n_points < 10:
            d = scipy.spatial.distance.pdist(data)
            return np.max(d)

        if n_partition is not None:
            random_indices = np.random.default_rng(random_state).permutation(n_points)
            distance_matrix = sd.cdist(data[random_indices[:n_partition]], data, metric='euclidean')
        else:
            distance_matrix = sd.squareform(sd.pdist(data, metric='euclidean'))
        k = np.min([n_nearest_neighbors, distance_matrix.shape[1]])
        k_smallest_values = np.partition(distance_matrix, k - 1, axis=1)[:, k - 1]

        est_cutoff = np.max(k_smallest_values)
        return float(est_cutoff)