import aerosandbox.numpy as np
from scipy.special import comb
from aerosandbox.geometry.polygon import stack_coordinates
import re

_default_n_points_per_side = 200

def get_NACA_coordinates(
        name: str = 'naca2412',
        n_points_per_side: int = _default_n_points_per_side
) -> np.ndarray:
    """
    Returns the coordinates of a specified 4-digit NACA airfoil.
    Args:
        name: Name of the NACA airfoil.
        n_points_per_side: Number of points per side of the airfoil (top/bottom).

    Returns: The coordinates of the airfoil as a Nx2 ndarray [x, y]

    """
    name = name.lower().strip()

    if not "naca" in name:
        raise ValueError("Not a NACA airfoil!")

    nacanumber = name.split("naca")[1]
    if not nacanumber.isdigit():
        raise ValueError("Couldn't parse the number of the NACA airfoil!")

    if not len(nacanumber) == 4:
        raise NotImplementedError("Only 4-digit NACA airfoils are currently supported!")

    # Parse
    max_camber = int(nacanumber[0]) * 0.01
    camber_loc = int(nacanumber[1]) * 0.1
    thickness = int(nacanumber[2:]) * 0.01

    # Referencing https://en.wikipedia.org/wiki/NACA_airfoil#Equation_for_a_cambered_4-digit_NACA_airfoil
    # from here on out

    # Make uncambered coordinates
    x_t = np.cosspace(0, 1, n_points_per_side)  # Generate some cosine-spaced points
    y_t = 5 * thickness * (
            + 0.2969 * x_t ** 0.5
            - 0.1260 * x_t
            - 0.3516 * x_t ** 2
            + 0.2843 * x_t ** 3
            - 0.1015 * x_t ** 4  # 0.1015 is original, #0.1036 for sharp TE
    )

    if camber_loc == 0:
        camber_loc = 0.5  # prevents divide by zero errors for things like naca0012's.

    # Get camber
    y_c = np.where(
        x_t <= camber_loc,
        max_camber / camber_loc ** 2 * (2 * camber_loc * x_t - x_t ** 2),
        max_camber / (1 - camber_loc) ** 2 * ((1 - 2 * camber_loc) + 2 * camber_loc * x_t - x_t ** 2)
    )

    # Get camber slope
    dycdx = np.where(
        x_t <= camber_loc,
        2 * max_camber / camber_loc ** 2 * (camber_loc - x_t),
        2 * max_camber / (1 - camber_loc) ** 2 * (camber_loc - x_t)
    )
    theta = np.arctan(dycdx)

    # Combine everything
    x_U = x_t - y_t * np.sin(theta)
    x_L = x_t + y_t * np.sin(theta)
    y_U = y_c + y_t * np.cos(theta)
    y_L = y_c - y_t * np.cos(theta)

    # Flip upper surface so it's back to front
    x_U, y_U = x_U[::-1], y_U[::-1]

    # Trim 1 point from lower surface so there's no overlap
    x_L, y_L = x_L[1:], y_L[1:]

    x = np.hstack((x_U, x_L))
    y = np.hstack((y_U, y_L))

    return stack_coordinates(x, y)

def get_kulfan_coordinates(
        lower_weights=-0.2 * np.ones(5),  # type: np.ndarray
        upper_weights=0.2 * np.ones(5),  # type: np.ndarray
        enforce_continuous_LE_radius=True,
        TE_thickness=0.005,  # type: float
        n_points_per_side=_default_n_points_per_side,  # type: int
        N1=0.5,  # type: float
        N2=1.0,  # type: float
) -> np.ndarray:
    """
    Calculates the coordinates of a Kulfan (CST) airfoil.
    To make a Kulfan (CST) airfoil, use the following syntax:

    asb.Airfoil("My Airfoil Name", coordinates = asb.kulfan_coordinates(*args))

    More on Kulfan (CST) airfoils: http://brendakulfan.com/docs/CST2.pdf
    Notes on N1, N2 (shape factor) combinations:
        * 0.5, 1: Conventional airfoil
        * 0.5, 0.5: Elliptic airfoil
        * 1, 1: Biconvex airfoil
        * 0.75, 0.75: Sears-Haack body (radius distribution)
        * 0.75, 0.25: Low-drag projectile
        * 1, 0.001: Cone or wedge airfoil
        * 0.001, 0.001: Rectangle, circular duct, or circular rod.
    :param lower_weights:
    :param upper_weights:
    :param enforce_continuous_LE_radius: Enforces a continous leading-edge radius by throwing out the first lower weight.
    :param TE_thickness:
    :param n_points_per_side:
    :param N1: LE shape factor
    :param N2: TE shape factor
    :return:
    """

    if enforce_continuous_LE_radius:
        lower_weights[0] = -1 * upper_weights[0]

    x_lower = cosspace(0, 1, n_points_per_side)
    x_upper = x_lower[::-1]

    def shape(w, x):
        # Class function
        C = x ** N1 * (1 - x) ** N2

        # Shape function (Bernstein polynomials)
        n = len(w) - 1  # Order of Bernstein polynomials

        K = comb(n, np.arange(n + 1))  # Bernstein polynomial coefficients

        S_matrix = (
                w * K * np.expand_dims(x, 1) ** np.arange(n + 1) *
                np.expand_dims(1 - x, 1) ** (n - np.arange(n + 1))
        )  # Polynomial coefficient * weight matrix
        S = np.sum(S_matrix, axis=1)

        # Calculate y output
        y = C * S
        return y

    y_lower = shape(lower_weights, x_lower)
    y_upper = shape(upper_weights, x_upper)

    # TE thickness
    y_lower -= x_lower * TE_thickness / 2
    y_upper += x_upper * TE_thickness / 2

    x = np.concatenate([x_upper, x_lower])
    y = np.concatenate([y_upper, y_lower])
    coordinates = np.vstack((x, y)).T

    return coordinates

def get_coordinates_from_raw_dat(raw_text) -> np.ndarray:
    """
    Returns a Nx2 ndarray of airfoil coordinates from the raw text of a airfoil *.dat file.
    Args:
        raw_text: The raw text of the *.dat file, as read by file.readlines()

    Returns: A Nx2 ndarray of airfoil coordinates [x, y].

    """
    raw_coordinates = []

    def is_number(s):  # determines whether a string is representable as a float
        try:
            float(s)
        except ValueError:
            return False
        return True

    for line in raw_text:
        try:
            line_split = re.split(r'[; |, |\*|\n]', line)
            line_items = [s for s in line_split
                          if s != "" and is_number(s)
                          ]
            if len(line_items) == 2:
                raw_coordinates.append(line_items)
        except:
            pass

    if len(raw_coordinates) == 0:
        raise ValueError("File was found, but could not read any coordinates!")

    coordinates = np.array(raw_coordinates, dtype=float)

    return coordinates

def get_file_coordinates(
        filepath
):
    try:
        with open(filepath, "r") as f:
            raw_text = f.readlines()
    except FileNotFoundError as e:
        try:
            with open(f"{filepath}.dat", "r") as f:
                raw_text = f.readlines()
        except FileNotFoundError as e:
            raise FileNotFoundError(
                f" Neither '{filepath}' nor '{filepath}.dat' were found."
            ) from e

    return get_coordinates_from_raw_dat(raw_text)

def get_UIUC_coordinates(
        name: str = 'dae11'
) -> np.ndarray:
    """
    Returns the coordinates of a specified airfoil in the UIUC airfoil database.
    Args:
        name: Name of the airfoil to retrieve from the UIUC database.

    Returns: The coordinates of the airfoil as a Nx2 ndarray [x, y]
    """

    name = name.lower().strip()

    import importlib.resources
    from aerosandbox.geometry.airfoil import airfoil_database

    try:
        with importlib.resources.open_text(airfoil_database, name) as f:
            raw_text = f.readlines()
    except FileNotFoundError as e:
        try:
            with importlib.resources.open_text(airfoil_database, name + '.dat') as f:
                raw_text = f.readlines()
        except FileNotFoundError as e:
            raise FileNotFoundError(
                f"Neither '{name}' nor '{name}.dat' were found in the UIUC airfoil database."
            ) from e

    return get_coordinates_from_raw_dat(raw_text)


import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from scipy.fftpack import fft, fft2, ifft
import scipy.io as sio
import copy

## general simulation parameters

fs = 1024
npnts = fs*5 # 5 seconds# parameters
freq = 4 # peak frequency

# centered time vector
timevec = np.arange(0,npnts)/fs
timevec = timevec - np.mean(timevec)

# for power spectrum
hz = np.linspace(0,fs/2,int(np.floor(npnts/2)+1))

## Difference of Gaussians (DoG)
# (approximation of Laplacian of Gaussian)

# define sigmas
sPos = .1
sNeg = .5

# create the two GAussians
gaus1 = np.exp( (-timevec**2) / (2*sPos**2) ) / (sPos*np.sqrt(2*np.pi))
gaus2 = np.exp( (-timevec**2) / (2*sNeg**2) ) / (sNeg*np.sqrt(2*np.pi))

# their difference is the DoG
DoG = gaus1 - gaus2

# amplitude spectrum
DoGPow = np.abs(fft(DoG)/npnts)

# time-domain plotting
plt.subplot(211)
plt.plot(timevec,DoG,'k')
plt.xlabel('Time (sec.)')
plt.title('DoG wavelet in time domain')

# frequency-domain plotting
plt.subplot(212)
plt.plot(hz,DoGPow[:len(hz)],'k')
plt.xlim([0,freq*3])
plt.xlabel('Frequency (Hz)')
plt.title('DoG wavelet in frequency domain')
plt.show()


import numpy as np

class SimpleIteration(object):
    """求解线性方程组的迭代法:
        A @ x = b

    调用 SimpleIteration 子类实例得到解, e.g. JacobiIteration(A, b)()

    """

    def __init__(self, A, b):
        """
        A:  np_array_like, 系数矩阵
        b:  np_array_like, 右端常数
        """
        self.A = np.array(A)
        self.b = np.array(b)

        n, m = self.A.shape
        if n != m or n != len(self.b):
            raise ValueError(f"Not match: A({n, m}) and b({len(b)},)")

    @staticmethod
    def _dlu(A):
        """分裂 A:
            A = D - L - U

        Args:
            A: np.array

        Returns:
            (D, L, U)
        """
        D = np.diag(np.diag(A))
        L = - np.tril(A, -1)
        U = - np.triu(A, 1)

        return D, L, U

    def _B_f(self):
        """计算迭代 x = B @ x + f 的 B 和 f
        """
        raise NotImplementedError('_B_f')

    def __call__(self, x0=None, eps=1e-5, max_steps=5000, verbose=False):
        """线性方程组「简单迭代法」的迭代过程
            x = B @ x + f

        其中 B, f 调用 self._B_f() 得到

        Args:
            x0: np_array_like, 迭代初值
                default x0=None means using random values.
            eps: float, 精度要求
            max_steps: int, 最大迭代次数
            verbose: bool, 如果计算成功，打印出结果及迭代次数

        Returns:
            x: 方程组的解

        Raises:
            ValueError: x0 形状不符合问题
            Expection:  达到最大迭代次数，仍不满足精度
        """
        if not x0:
            x0 = np.random.random(self.A.shape[0])

        x0_shape = np.shape(x0)
        if x0_shape[0] != self.A.shape[0]:
            raise ValueError(
                f"Not match: A({self.A.shape}) and x0({x0_shape})")

        B, f = self._B_f()

        x = x0
        i = 0
        for i in range(int(max_steps)):
            x_prev = np.array(x)  # deep copy

            x = B @ x + f

            if np.all(np.abs(x - x_prev) <= eps):  # 达到精度要求
                break
        else:
            raise Exception(
                f"{self.method_name()} cannot reach eps ({eps}) after max_steps ({max_steps}). The last result: x = {x}")

        if verbose:
            print(f"{self.method_name()} get result x = {x} after {i} iterations.")

    def method_name(self):
        return self.__class__.__name__

class JacobiIteration(SimpleIteration):
    """雅可比（Jacobi）迭代法求解线性方程组:
        A @ x = b
    """

    def _B_f(self):
        D, L, U = self._dlu(self.A)

        inv_D = np.linalg.pinv(D)

        B = inv_D @ (L + U)
        f = inv_D @ self.b

        return B, f

class GaussSeidel(SimpleIteration):
    """高斯-赛德尔迭代（Gauss–Seidel method）求解线性方程组:
        A @ x = b
    """

    def _B_f(self):
        D, L, U = self._dlu(self.A)

        inv_DsL = np.linalg.pinv(D - L)

        B = inv_DsL @ U
        f = inv_DsL @ self.b

        return B, f

class SOR(SimpleIteration):
    """逐次超松驰法（Successive over-relaxation, SOR）求解线性方程组:
        A @ x = b
    """

    def __init__(self, A, b, w=1):
        """
        w:  float, 松弛因子 w > 0
            default w=1，即 Gauss–Seidel 迭代
        """
        super().__init__(A, b)
        self.w = w

    def _B_f(self):
        D, L, U = self._dlu(self.A)
        w = self.w

        _inv_DsWL = np.linalg.pinv(D - w * L)

        B = _inv_DsWL @ ((1-w) * D + w * U)
        f = w * _inv_DsWL @ self.b

        return B, f

    def method_name(self):
        return super().method_name() + f' (w={self.w})'


"""
inner

The inner tool returns the inner product of two arrays.

import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print numpy.inner(A, B)     #Output : 4

outer

The outer tool returns the outer product of two arrays.

import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print numpy.outer(A, B)     #Output : [[0 0]
                            #          [3 4]]

Task

You are given two arrays:
and

.
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.

Sample Input

0 1
2 3

Sample Output

3
[[0 0]
 [2 3]]
"""

import numpy as np
A = np.array(input().split(), int)
B = np.array(input().split(), int)
print(np.inner(A,B), np.outer(A,B), sep='\n')

# or like this
import numpy

A, B = [numpy.array(input().split(), int) for _ in range(2)]
print('\n'.join(str(op(A, B)) for op in (numpy.inner, numpy.outer)))


# https://en.wikipedia.org/wiki/Test_functions_for_optimization

import numpy as np

# Rastrigin

def rastrigin(x):
    x = np.array(x)
    return 10 * len(x) + np.sum(x ** 2 - 10 * np.cos(2 * np.pi * x), axis=0)

def rastrigin_bounds(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------
    list of 2-tuples
    """
    return [(-5.12, 5.12) for _ in range(d)]

def rastrigin_ySol(d):
    return 0

def rastrigin_xSol(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return np.zeros((d,))

# Sphere

def sphere(x):
    x = np.array(x)
    return np.sum(x ** 2)

def sphere_bounds(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return [(-10., 10.) for _ in range(d)]

def sphere_ySol(d):
    return 0

def sphere_xSol(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return np.zeros((d,))

# Rosenbrock

def rosenbrock(x):
    x = np.array(x)
    summation = 0
    for i in range(len(x)-1):
        summation += 100*((x[i+1] - x[i]**2)**2) + (1 - x[i])**2

    return summation

def rosenbrock_bounds(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return [(-10., 10.) for i in range(d)]

def rosenbrock_ySol(d):
    return 0

def rosenbrock_xSol(d):
    return np.ones((d,))

# Styblinski

def styblinski(x):
    x = np.array(x)
    return np.sum(x ** 4 - 16 * x ** 2 + 5 * x) / 2

def styblinski_bounds(d):
    """

    Parameters
    ----------
    d : int
        dimension

    Returns
    -------

    """
    return [(-5., 5.) for _ in range(d)]

def styblinski_ySol(d):
    return -39.16616 * d

def styblinski_xSol(d):
    return np.ones((d,)) * -2.903534

all_functions_string = ['rastrigin', 'sphere', 'rosenbrock', 'styblinski']

all_functions = [eval(function_string) for function_string in all_functions_string]

all_bounds = [eval(function_string+'_bounds') for function_string in all_functions_string]

all_xSol = [eval(function_string+'_xSol') for function_string in all_functions_string]

all_ySol = [eval(function_string+'_ySol') for function_string in all_functions_string]


from Statistics.Mean import mean
from Calculator.Multiplication import multiplication
from Calculator.Subtraction import subtraction
from Calculator.Square import square
from Calculator.Division import division
from Calculator.SquareRoot import square_root
from pprint import pprint
import numpy as np

def population_correlation(data, data1):
    try:
        mean1 =mean(data)
        mean2 =mean(data1)
        #pprint(mean1)
        #pprint(mean2)
        List1=[]
        List2=[]

        for num in data:
            a = subtraction(int(round(mean1,0)),num)
            List1.append(a)

        for num in data1:
            b = subtraction(mean2,num)
            List2.append(b)
        c = np.multiply(List1, List2)
        cc=0

        for num in c:
            cc= cc+num

        d=0
        e=0

        for num in List1:
            d = d+ square(num)
        for num in List2:
            e = e +square(num)

        f = multiplication(int(d), e)
        g = square_root(int(f))
        h = division(int(g),cc)

        nCorrelation = round(h,9)
        pprint(nCorrelation)
        return nCorrelation
    except ZeroDivisionError:
        print("Error - Cannot divide by 0")
    except ValueError:
        print("Error - Invalid data inputs")

#https://www.youtube.com/watch?v=eIZD1BFfw8E population vs sample
#https://www.statisticshowto.datasciencecentral.com/probability-and-statistics/correlation-coefficient-formula/


import numpy as np
import numpy.polynomial.polynomial as poly
from matplotlib import pyplot as plt

class Predictor:

    def __init__(self, days, data):
        data = data.transpose()
        self.x = data[:, 0:1].copy()
        self.y = data[:, 1:2].copy()
        self.days = days

    def polynomial_regression(self, n):
        # get regression
        first_col = np.ones((self.x.shape[0], 1))
        a = np.append(first_col, self.x, axis=1)

        if n >= 2:
            for i in range(2, n+1):
                a = np.append(a, self.x**i, axis=1)

        b = self.y
        a2 = np.matmul(a.transpose(), a)
        b2 = np.matmul(a.transpose(), b)
        c = np.matmul(np.linalg.inv(a2), b2)
        c = c.transpose()[0]
        val = np.arange(1, self.days+1, dtype=float)
        val += self.x[-1]
        val = val.reshape((1, val.shape[0]))
        val = np.append(self.x.transpose(), val, axis=1)

        return np.array([val[0], poly.polyval(val[0], c)])

    def exponential_regression(self):
        # get regression
        first_col = np.ones((self.x.shape[0], 1))
        a = np.append(first_col, self.x, axis=1)

        temp = self.y.copy()

        for i in range(self.y.shape[0]):
            if self.y[i, 0] <= 0:
                self.y[i, 0] = 0.001

        b = np.log(self.y)
        a2 = np.matmul(a.transpose(), a)
        b2 = np.matmul(a.transpose(), b)
        c = np.matmul(np.linalg.inv(a2), b2)
        c = np.exp(c.transpose()[0])

        val = np.arange(1, self.days+1, dtype=float)
        val += self.x[-1]
        val = val.reshape((1, val.shape[0]))
        val = np.append(self.x.transpose(), val, axis=1)

        self.y = temp

        return np.array([val[0], c[0]*np.power(c[1], val[0])])

    def derivative(self):
        der = np.array([])

        n = self.y.shape[0]

        for i in range(n):
            if i == 0:
                der = np.append(der, self.y[i, 0])
            else:
                der = np.append(der, self.y[i, 0] - self.y[i-1, 0])

        return der

    def gaussian(self):
        """Adjusting a Gaussian function to the daily increment
        f(x) = A * exp((-(x-B)^2)/(2*C^2))

        :return:
        """

        d = np.expand_dims(self.derivative(), axis=1)
        der_x = self.x.copy()

        # data preprocessing
        while d[0] == 0:
            # remove zeros at start
            d = np.delete(d, 0, axis=0)
            der_x = np.delete(der_x, 0, axis=0)

            if d.size == 0:
                break

        for i in reversed(range(d.shape[0])):
            if d[i] <= 0:
                d = np.delete(d, i, axis=0)
                der_x = np.delete(der_x, i, axis=0)

        if der_x.shape[0] == 0:
            return np.array([[0], [0]])

        der_y = np.log(d)

        # get regression
        first_col = np.ones((der_x.shape[0], 1))
        a = np.append(first_col, der_x, axis=1)

        for i in range(2, 3):
            a = np.append(a, der_x**i, axis=1)

        b = der_y
        a2 = np.matmul(a.transpose(), a)
        b2 = np.matmul(a.transpose(), b)

        c = np.matmul(np.linalg.pinv(a2), b2)

        if c[2] == 0:
            c[2] = 0.001

        # square of c2
        c2_2 = -1/(2*c[2])

        c1 = c[1]*(-1/(2*c[2]))
        c0 = np.exp(c[0]+0.5*(c1**2/c2_2))

        val = np.arange(1, self.days+1, dtype=float)
        val += self.x[-1, :]
        val = val.reshape((val.shape[0], 1))
        val = np.append(self.x, val, axis=0)

        y2 = c0*np.exp((-(val - c1)**2)/(2*c2_2))
        plt.plot(val, y2, 'r')

        return np.array([val.transpose()[0], y2.transpose()[0]])

if __name__ == "__main__":
    p = Predictor(30, np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144]]))
    # print(p.polynomial_regression(2))
    # print(p.exponential_regression())
    print(p.gaussian())


import pathlib

import numpy as np
import pandas as pd
import scipy.stats as stats

src_dir = pathlib.Path(__file__).resolve().parent.parent
dat_path = src_dir.joinpath("data", "clouds.dat")

df = pd.read_table(dat_path, sep="\s+", skiprows=29)
unseeded_rainfall, seeded_rainfall = df["Unseeded_Clouds"], df["Seeded_Clouds"]

alpha = 0.05
z = stats.norm.ppf(1 - alpha / 2)

unseeded_mean = unseeded_rainfall.mean()
unseeded_mean_se = unseeded_rainfall.std() / np.sqrt(unseeded_rainfall.size)
seeded_mean = seeded_rainfall.mean()
seeded_mean_se = seeded_rainfall.std() / np.sqrt(seeded_rainfall.size)

diff = seeded_mean - unseeded_mean
diff_se = np.sqrt(unseeded_mean_se ** 2 + seeded_mean_se ** 2)
diff_lower_bound = diff - z * diff_se
diff_upper_bound = diff + z * diff_se

print(f"The mean waiting time is approximately {diff:.4}.")
print(
    "A 95% CI for this value is given by",
    f"[{diff_lower_bound:.4}, {diff_upper_bound:.4}].",
)


import numpy as np
import numba

@numba.jit(nopython=True, parallel=True)
def tridiagonal_invers(alpha, beta, gamma, b):
    # solution to Ax=b, where A is [n x n] tridiagonal matrix with values alpha, beta, gamma
    # alpha: [n x 1] numpy vector  of main diagonal of A
    # beta: [(n-1) x 1] numpy vector of diagonal above main diagonal (+1)
    # gamma: [(n-1) x 1] numpy vector of diagonal below main diagonal (-1)
    # b: [n x 1] numpy vector

    # returns x: [n x 1] numpy vector (solution to Ax=b)
    # ------------------------------------------------------------------------------------------------------------------
    n = len(alpha)
    alpha_hat = np.zeros(n, dtype=np.float64)
    b_hat = np.zeros(n, dtype=np.float64)

    alpha_hat[0] = alpha[0]
    b_hat[0] = b[0]

    for i in range(1, n):
        alpha_hat[i] = alpha[i] - gamma[i - 1] * beta[i - 1] / alpha_hat[i - 1]
        b_hat[i] = b[i] - gamma[i - 1] * b_hat[i - 1] / alpha_hat[i - 1]

    x = np.ones(n, dtype=np.float64)
    x[-1] = b_hat[-1] / alpha_hat[-1]

    for i in range(n - 2, -1, -1):
        x[i] = (b_hat[i] - beta[i] * x[i + 1]) / alpha_hat[i]
    return x

@numba.jit(nopython=True, parallel=True)
def brennon_schwartz_algorithm(alpha, beta, gamma, bs, g_nui):
    # Solution to Ax-b >= 0 ; x >= g and (Ax-b)'(x-g)=0 ; such that solution x satisfies x_i = g_i for i=1,...,k and x_i > g_i for i = k-1,...,n
    #  where A is tridiagonal with values alpha, beta, gamma
    # alpha: [n x 1] numpy vector  of main diagonal of A
    # beta: [(n-1) x 1] numpy vector of diagonal above main diagonal (+1)
    # gamma: [(n-1) x 1] numpy vector of diagonal below main diagonal (-1)
    # b: [n x 1] numpy vector

    # returns solution x: [n x 1] numpy vector
    # ------------------------------------------------------------------------------------------------------------------
    n = len(alpha)
    alpha_hat = np.zeros(n, dtype=np.float64)
    b_hat = np.zeros(n, dtype=np.float64)

    alpha_hat[-1] = alpha[-1]
    b_hat[-1] = bs[-1]

    for i in range(n - 2, -1, -1):
        alpha_hat[i] = alpha[i] - beta[i] * gamma[i] / alpha_hat[i + 1]
        b_hat[i] = bs[i] - beta[i] * b_hat[i + 1] / alpha_hat[i + 1]

    x = np.zeros(n, dtype=np.float64)
    x[0] = np.maximum(b_hat[0] / alpha_hat[0], g_nui[0])
    for i in range(1, n):
        x[i] = np.maximum((b_hat[i] - gamma[i - 1] * x[i - 1]) / alpha_hat[i], g_nui[i])
    return x


import numpy as np

def sigmoid(A):
    return 1 / (1 + np.exp(-A))

def dsigmoid(A):
    B = sigmoid(A)
    return B * (1 - B)

def tanh(A):
    return 2 * sigmoid(2 * A) - 1

def dtanh(A):
    return 4 * dsigmoid(2 * A)

def relu(A):
    return np.maximum(A, 0)

def drelu(A):
    return np.sign(relu(A + 0.00001))

if __name__ == "__main__":
    assert np.equal(sigmoid(0), 0.5)
    assert np.allclose(sigmoid(np.array([0, np.log(3), -np.log(3)])), np.array([ 0.5, 0.75, 0.25]))
    assert np.allclose(dsigmoid(np.array([0, 100, -100])), np.array([ 0.25, 0, 0]))
    assert np.equal(tanh(0), 0)
    assert np.isclose(dtanh(1), 1 - tanh(1)**2)
    assert np.equal(relu(-1), 0)
    assert np.equal(relu(1), 1)
    assert np.array_equal(drelu(np.array([-1, 2])), np.array([0, 1]))


import numpy as np
import numpy.linalg
import scipy as sp
import scipy.linalg

# Kernel (aka nullspace) of a matrix
# Construct an n-by-n matrix with a kernel of dimension r (r<n)
n = 7
r = 3
A = np.random.randint(-30, 31, size=[n - r, n])
A = np.vstack((A, np.random.randint(-5, 5, size=[r, n - r]) @ A))

# Determine the kernel of A (spanned by the columns of N)
N = sp.linalg.null_space(A)

# This means that all vectors of the form x = N*z are in the kernel of A
# Coversely, for any vector x in the kernel of A, there is a z such that
# x = N*z. For example:
z0 = np.random.randn(r, 1)
x0 = N @ z0
if not np.linalg.norm(A @ x0, np.inf) < 1e-10:
    print('x0 error')

# Projection on kernel
# Now we want to project a given vector x onto the kernel of A
# The projection is given by

def proj_ker(x_):
    return N @ numpy.linalg.lstsq(N, x_, rcond=None)[0]

# So, for example:
x = np.random.randn(n, 1)
x_ker = proj_ker(x)

# This means we can write any x in R^n as x = x_ker + x_im, where x_ker is
# in the kernel of A and x_im is in its image. Additionally, x_ker will be
# perpendicular to x_im:
x_im = x - x_ker
if not abs(x_im.T @ x_ker) < 1e-10:
    print('x_im error')


import numpy as np
from numpy.typing import NDArray

from .base import BaseActivation

class Identity(BaseActivation):
    @staticmethod
    def _func(x):
        return x.copy()

    @classmethod
    def _derivative(cls, x):
        return np.ones_like(x)

class Sigmoid(BaseActivation):
    @staticmethod
    def _func(x: np.ndarray):
        # return 1 / (1 + np.exp(-x))
        return np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))

    @classmethod
    def _derivative(cls, x):
        return cls._func(x) * (1 - cls._func(x))

class ReLu(BaseActivation):
    @staticmethod
    def _func(x):
        # return np.max(0, x_)
        # return np.clip(x_, 0, np.inf)
        return np.maximum(0, x)

    @staticmethod
    def _derivative(x: np.ndarray) -> np.ndarray:
        # TODO fix
        return (x > 0).astype(int)

class LeakyReLu(BaseActivation):
    @staticmethod
    def _func(x: np.ndarray) -> np.ndarray:
        # return np.max(0.1 * x_, x_)
        # return np.clip(x_, 0.01 * x_, np.inf)
        return np.maximum(0.01 * x, x)

    @staticmethod
    def _derivative(x: np.ndarray) -> np.ndarray:
        _x = x.copy().astype(float)
        np.putmask(_x, x >= 0, 1)
        np.putmask(_x, x < 0, 0.01)
        return _x

# following activation-functions' underlying formulas
# from 'https://www.v7labs.com/blog/neural-networks-activation-functions'
class Tanh(BaseActivation):
    @staticmethod
    def _func(x: np.ndarray) -> np.ndarray:
        return np.tanh(x)

    @staticmethod
    def _derivative(x: np.ndarray) -> np.ndarray:
        return 1 - np.square(np.tanh(x))

class SoftMax(BaseActivation):
    @staticmethod
    def _func(x: np.ndarray) -> np.ndarray:
        # return np.exp(x) / np.sum(np.exp(x))
        # numerically more stable, it is an identity
        return np.exp(x - np.max(x)) / np.sum(np.exp(x))

    @staticmethod
    def _sigmoid(x: np.ndarray) -> np.ndarray:
        return np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))

    @classmethod
    def _derivative(cls, x: np.ndarray) -> np.ndarray:
        return cls._sigmoid(x) * (1 - cls._sigmoid(x))

class Swish(BaseActivation):
    @classmethod
    def _func(cls, x: np.ndarray) -> np.ndarray:
        return x * cls._sigmoid(x)

    @staticmethod
    def _sigmoid(x: np.ndarray) -> np.ndarray:
        # return 1 / (1 + np.exp(-x))
        return np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))

    @classmethod
    def _sigmoid_deriv(cls, x: np.ndarray) -> np.ndarray:
        return cls._sigmoid(x) * (1 - cls._sigmoid(x))

    @classmethod
    def _derivative(cls, x: np.ndarray) -> np.ndarray:
        pass  # TODO: just simple chain rule, need to simplify

class SoftMax2(BaseActivation):
    @staticmethod
    def _exp(x: NDArray) -> NDArray:
        return np.exp(x - np.max(x))

    @classmethod
    def _func(cls, x: np.ndarray) -> np.ndarray:
        exps = cls._exp(x)
        return exps / np.sum(exps, axis=0)

    @classmethod
    def _derivative(cls, x: np.ndarray) -> np.ndarray:
        exps = cls._exp(x)
        return exps / np.sum(exps, axis=0) * (1 - exps / np.sum(exps, axis=0))


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

# # ml lab2

# In[25]:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt

pd.set_option('display.max_rows', 10)

# ### 1. read ex1 data

# In[296]:

data1 = pd.DataFrame(np.genfromtxt('data/ex2data1.txt', delimiter=','), columns=['Grade1', 'Grade2', 'Accepted'])
data1

# ### 2. plot data

# In[297]:

def get_plot(data, x_label = 'Grade1', y_label = 'Grade2'):
    plt.figure()
    ax = data.loc[data.Accepted == 1].plot(x=x_label, y=y_label, style=['gx'], label='Accepted')
    ay = data.loc[data.Accepted == 0].plot(x=x_label, y=y_label, style=['rx'], label='Not Accepted', ax=ax)
    ax.set_xlabel(x_label)
    ax.set_ylabel(y_label)
    plt.legend()
    return plt, ax

def plot_data(data, label_x='Grade1', label_y='Grade2', axes=None):
    pos = data.loc[data.Accepted == 1]
    neg = data.loc[data.Accepted == 0]

    if axes == None:
        axes = plt.gca()

    axes.scatter(pos[label_x], pos[label_y], marker='x', c='g', s=30, label='Accepted')
    axes.scatter(neg[label_x], neg[label_y], c='r', s=30, label='Not Accepted')
    axes.set_xlabel(label_x)
    axes.set_ylabel(label_y)
    axes.legend(frameon= True, fancybox = True)

plot_data(data1)

# ### 3. cost function J(θ) + gradient descent

# In[298]:

def sigmoid(X):
    return 1 / (1 + np.exp(-X))

def cost_function(theta, X, y):
    m = y.size
    h = sigmoid(X @ theta)
    J = (-1 / m) * ((y.T @ np.log(h)) + ((1 - y).T @ np.log(1 - h)))
    error = h - y
    gd = (1 / m) * (X.T @ error)
    return J, gd

# In[299]:

m, n = data1.shape
initial_theta = np.zeros(n)

X = data1[['Grade1', 'Grade2']]
X.insert(0, 'theta_0', 1)
y = data1['Accepted']

cost, gd = cost_function(initial_theta, X, y)
print(f'Cost with initial_theta:\t{cost}')
print(f'Gradient with initial_theta:\t{gd.ravel()}')

# ### 4. optimize cost

# In[300]:

def optimize_cost(theta, X, y):
    cost, _ = cost_function(theta, X, y)
    return cost

def optimize_gradient(theta, X, y):
    _, gd = cost_function(theta, X, y)
    return gd

def optimize(func, gradient, X, y, method):
    initial_theta = np.zeros(n)

    result = opt.minimize(fun=func, x0=initial_theta, args=(X, y), method=method, jac=gradient)
    theta = result.x
    cost = func(theta, X, y)

    print(f'theta:\t{theta.ravel()}\ncost:\t{cost}')
    return result

# #### Nelder-Mead

# In[301]:

result = optimize(optimize_cost, optimize_gradient, X, y, 'Nelder-Mead')
result

# #### Broyden-Fletcher-Goldfarb-Shanno

# In[302]:

result = optimize(optimize_cost, optimize_gradient, X, y, 'BFGS')
result

# ### 5. predict function

# In[303]:

def predict(theta, X):
    predict = sigmoid(np.dot(X, theta))
    return predict > 0.5

theta = result.x
sample = np.array([1, 57, 79])
probability = sigmoid(sample @ theta)
print(f'With Grades {sample[1:]}, probability of acceptance is: {probability}')

# In[304]:

def get_accuracy(theta, X, y):
    p = predict(theta, X)
    return y[p == y].size / y.size * 100
print(f'Train Accuracy: {get_accuracy(theta, X, y)}%')

# ### 6. decision surface

# In[305]:

plot, ax = get_plot(data1)
slope = -(theta[1] / theta[2])
intercept = -(theta[0] / theta[2])

xs = np.array(ax.get_xlim())
ys = intercept + (slope * xs)

plot.plot(xs, ys);
plot.show()

# ### 7. read ex2 data

# In[306]:

data2 = pd.DataFrame(np.genfromtxt('data/ex2data2.txt', delimiter=','), columns=['Test1', 'Test2', 'Accepted'])
data2

# ### 8. plot data

# In[307]:

plot_data(data2, 'Test1', 'Test2')

# ### 9. map features

# In[308]:

def map_feature(x1, x2, degree=6):
    '''
    Maps the two input features to quadratic features.
    Returns features array:
        x1, x2, x1**2, x2**2, x1*x2, x1*x2**2...
    '''
    x1.shape = (x1.size, 1)
    x2.shape = (x2.size, 1)
    result = np.ones(shape=(x1[:, 0].size, 1))
    m, n = result.shape

    for i in range(1, degree + 1):
        for j in range(i + 1):
            r = (x1 ** (i - j)) * (x2 ** j)
            result = np.append(result, r, axis=1)

    return result

x1 = np.array(data2['Test1'])
x2 = np.array(data2['Test2'])
mapped = map_feature(x1, x2)
mapped.shape

# ### 10. l2 regularization with gradient descent

# In[309]:

def cost_function_reg(theta, X, y, l=1):
    h = sigmoid(X.dot(theta))
    thetaR = theta[1:, 0]

    J = (-1.0 / m) * ((y.T @ np.log(h)) + ((1 - y.T) @ np.log(1.0 - h))) + (l / (2.0 * m)) * (thetaR.T @ thetaR)

    y.shape = h.shape
    delta = h - y
    sumdelta = delta.T @ X[:, 1]
    grad1 = (1.0 / m) * sumdelta

    XR = X[:, 1:X.shape[1]]
    sumdelta = delta.T @ XR

    grad = (1.0 / m) * (sumdelta + l * thetaR)

    out = np.zeros(shape=(grad.shape[0], grad.shape[1] + 1))

    out[:, 0] = grad1
    out[:, 1:] = grad

    return J.flatten(), out.T.flatten()

def gradient_reg(theta, X, y, l=1, alpha=0.2, num_iters=400):
    m = y.size
    j_history = []
    for i in range(0, num_iters):
        cost, grad = cost_function_reg(theta, X, y, l)
        grad.shape = theta.shape
        theta -= alpha * grad
        j_history.append(cost)

    return theta, j_history

# In[310]:

m, n = data2.shape

y = np.array(data2['Accepted'])
y.shape = (m, 1)
initial_theta = np.zeros(shape=(mapped.shape[1], 1))

cost, _ = cost_function_reg(initial_theta, mapped, y)
print(f'Initial Cost:\t{cost}')

gd_theta, _ = gradient_reg(initial_theta, mapped, y)

# ### 11. other regularization functions

# In[311]:

def reshape_decorator(return_type):
    def reshape_theta(theta, X, y):
        theta.shape = (mapped.shape[1], 1)
        cost, grad = cost_function_reg(theta, X, y)
        theta.shape = (mapped.shape[1],)
        return cost if return_type == 'cost' else grad
    return reshape_theta

result = opt.minimize(
    fun=reshape_decorator('cost'),
    x0=initial_theta,
    args=(mapped, y),
    method='BFGS',
    jac=reshape_decorator('gd')
)
bfgs_theta = result.x
bfgs_theta.shape = (mapped.shape[1], 1)

# ### 12. predict function

# In[313]:

print(f'GD Train Accuracy: {get_accuracy(gd_theta, mapped, y)}%')
print(f'BFGS Train Accuracy: {get_accuracy(bfgs_theta, mapped, y)}%')

# ### 13-14. decision surface + different  λ

# In[328]:

def get_contour(theta):
    u = np.linspace(-1, 1.5)
    v = np.linspace(-1, 1.5)
    z = np.zeros(shape=(len(u), len(v)))
    for i in range(len(u)):
        for j in range(len(v)):
            z[i, j] = map_feature(np.array(u[i]), np.array(v[j])).dot(np.array(theta))
    return u, v, z.T

# In[329]:

LAMBDAS = [0, 1, 5, 50]
fig, axes = plt.subplots(2, 2, sharey = True, figsize=(12, 10))

for i, reg_lambda in enumerate(LAMBDAS):
    initial_theta = np.zeros(shape=(mapped.shape[1], 1))
    theta, _ = gradient_reg(initial_theta, mapped, y, reg_lambda, 0.2, 1000)
    accuracy = np.round(get_accuracy(theta, mapped, y), decimals=2)

    plot_data(data2, 'Test1', 'Test2', axes.flatten()[i])
    u, v, z = get_contour(theta)

    axes.flatten()[i].contour(u, v, z, colors='b', linewidths=1);
    axes.flatten()[i].set_title(f'Train accuracy {accuracy}% with Lambda={reg_lambda}')

# ### 15. read ex3 data

# In[330]:

import scipy.io

data3 = scipy.io.loadmat('data/ex2data3.mat')
x = np.array(data3['X'])
y = np.squeeze(data3['y'])
np.place(y, y == 10, 0)
n = x.shape[1]
m = x.shape[0]
labels_count = 10

# ### 16. visualize data

# In[353]:

import matplotlib.image as mpimg

X = np.ones(shape=(x.shape[0], x.shape[1] + 1))
X[:, 1:] = x

sample = np.random.choice(X.shape[0], 20)
plt.imshow(X[sample,1:].reshape(-1, 20).T, cmap='binary')
plt.axis('off');

# ### 17-18. vectorizing logistic regression + L2

# ### 19. one-vs-all classification

# In[354]:

classifiers = np.zeros(shape=(labels_count, n + 1))
for i in range(0, labels_count):
    label = (y == i).astype(int)
    initial_theta = np.zeros(shape=(X.shape[1], 1))
    theta, costs = gradient_reg(initial_theta, X, label, 0.4, 2.8, 10000)
    classifiers[i, :] = np.squeeze(theta)

# ### 20. predict function

# In[356]:

def predict_class(input, classifiers):
    class_probs = sigmoid(input @ classifiers.transpose())
    if len(class_probs.shape) == 1:
        class_probs.shape = (1, class_probs.shape[0])
    predictions = class_probs.argmax(axis=1)
    return predictions

# ### 21. training accuracy

# In[358]:

predictions = predict_class(X, classifiers)
accuracy = 100 * np.mean(predictions == y)
print(f'Training accuracy: {accuracy}')

# ### 22. conclusion

# В данной лабораторной работе рассмотрена модель логистической регрессии.
# Реализована `L2`-регуляризация, реализован бинарный классификатор с помощью логистической регрессии, который использовался для реализации многоклассового классификатора по методу "один против всех".
#
# В пункте #14 показана зависимость результатов от параметра регуляризации `λ`: переобучение или недообучение выборки.

# In[ ]:


# have to import because you need it!
import numpy as np

def calculate_distance(pointA, pointB):
    """
    This function calculates the distance between two points.

    Parameters
    ----------
    pointA, pointB: np.ndarray
        The coordinates of each point.

    Returns
    -------
    distance : float
        The distance between two points.

    Examples
    --------
    >>> r1 = np.array([0,0,0])
    >>> r2 = np.array([3,0,0])
    >>> calculate_distance(r1,r2)
    3.0
    """

    dist_vec = (pointA - pointB)
    distance = np.linalg.norm(dist_vec)
    return distance

def calculate_angle(pointA, pointB, pointC, degrees = False):
    # Calculate the angle between three points. Answer is given in radians by default, but can be given in degrees
    # by setting degrees = True
    AB = pointB - pointA
    BC = pointB - pointC
    theta = np.arccos(np.dot(AB, BC) / (np.linalg.norm(AB) * np.linalg.norm(BC)))

    if degrees:
        return np.degrees(theta)
    else:
        return theta


import pylab as p
import pandas as pd
import numpy as np
def lv_evaluate(beta,N,gamma,T,i0=1):
    '''
    SIR model simulation
    '''
    s=[]
    i=[]
    r=[]
    s.append(N-i0)
    i.append(i0)
    r.append(0)

    for t in range(T):
        s.append(s[t]-beta*s[t]*i[t])
        i.append(i[t]+beta*s[t]*i[t]-gamma*i[t])
        r.append(N-s[t+1]-i[t+1])
    return s,i,r

result  = pd.read_csv('result_20210528.csv',sep=';')
f       = result.scenario.unique()
tit     = ['Modello pre-covid', "Modello D'Assisi",'Modello Levi Civita']

def sir_beta(classe,sim_id,N):
    '''
    #plot sir data simulation
    '''
    c       = result[result.scenario==f[sim_id]]['z'][result.classe==classe].mean()
    rho     = [0.18,0.04,0.04]
    cl      = {'h':0,'m':1,'l':2}
    q       = result[result.scenario==f[sim_id]]['z'][result.classe==classe].sum()/result[result.scenario==f[sim_id]]['z'].sum()
    beta    = rho[cl[classe]] *q* c/N
    return beta

def plot_sir(classe,sim_id,T,N,gamma,i0):
    '''
    #plot sir data simulation
    '''
    beta      = sir_beta(classe=classe,sim_id=sim_id,N=N)
    r0      = (N-1)*beta/gamma
    ss,ii,rr = lv_evaluate(beta=beta,N=N,gamma=gamma,T=T,i0=i0)
    t = range(T+1)

    p.plot(t, ss, 'r-', label='Suscettibili')
    p.plot(t, ii, 'b-', label='Infettati')
    p.plot(t, rr, 'y-', label='Rimossi')
    p.plot(t, max(ii)*np.ones(len(t)), 'b--', label='Imax ('+str(round(max(ii),0))+')')
    p.grid()
    p.legend(loc='best')
    p.xlabel('tempo[gg]')
    p.ylabel('Individui')
    p.suptitle('Evoluzione nei compartimenti ('+tit[sim_id]+')')
    p.title('R0='+str(round(r0,3))+')')
    p.show()
    return max(ii)

T       = 60
N       = 400
gamma   = 1/14
i0=1
i0=plot_sir(classe='h',sim_id=0,T=T,N=N,gamma=gamma,i0=i0)
plot_sir(classe='h',sim_id=1,T=T,N=N,gamma=gamma,i0=i0)
plot_sir(classe='h',sim_id=2,T=T,N=N,gamma=gamma,i0=i0)

i0=1
plot_sir(classe='h',sim_id=0,T=T,N=N,gamma=gamma,i0=i0)
plot_sir(classe='h',sim_id=1,T=T,N=N,gamma=gamma,i0=i0)
plot_sir(classe='h',sim_id=2,T=T,N=N,gamma=gamma,i0=i0)


import numpy as np

# function to find the closest vertex of a POI
def getNeighbor(p, pol, kernel_fract = 0.01, return_index = False):
    """
    Function to find the closest vertex of polgyon to a POI.
    The closest neighbor is found by taking all vertices of a polygon which are located
    within a distance r from the POI. The distance r is increased step by step (by the kernel_fract)
    untill a point is found. Therefore, the kernel_fract does not affect the result but it is meant
    to optimize the computational time of the procedure.

    Author: Ignace Pelckmans (University of Antwerp, Belgium)

    Args:
        p: (Required) 1x2 Numpy Array, tuple or list with x- and y-coordinates of the poin of interest (POI)
        pol: (Required) nx2 numpy array with x- and y-coordinates of the polygon's vertices
        kernel_fract: (Optional) stepsize of the kernel size (% of the total width/height of the polygon). The kernel stepsize is the size to increase the searching circle around POI.
                                 It does not affect the final result is meant to optimize the computational time of the procedure.
        return_index: (Optional, defaults to False) return the index of the neighbor as well

    Returns:
        Numpy array of dimensions 1 x 2 with the x- and y-coordinate of the closest vertex of the polygon to the POI

    """
    # get width and height of channel polygon
    width = np.max(pol[:, 0]) - np.min(pol[:, 0])
    height = np.max(pol[:, 1]) - np.min(pol[:, 1])

    # parse to separate x- and y-coordinate
    px, py = p

    # stepsize to increase kernel
    ks = kernel_fract* max(width, height)

    # initialize a flag to feed the while loop and start a counter
    neigh_flag = False; t = 0
    # run while loop as long as there are no neighbouring vertices selected
    while neigh_flag == False:

        # count
        t += 1

        # create a square mask to only calculate the distances between the POI and nearby polygon vertices
        neigh_mask = (pol[:,0] > px - t*ks) * (pol[:,0] < px + t*ks) \
                * (pol[:,1] > py - t*ks) * (pol[:,1] < py + t*ks)
        # mask on all vertices
        neigh = pol[neigh_mask]

        # calculate the distance between the POI en all vertices after masking
        dist = (np.sum((neigh - p) ** 2, axis=1)) ** 0.5
        # mask a circle within the square kernel
        circle_mask = (dist <= t*ks/2)
        neigh = neigh[circle_mask]
        dist = dist[circle_mask]

        # terminate loop if there is at least one vertex in the neighbouring circle, otherwise increase the kernel size
        if np.shape(neigh)[0] > 0:
            neigh_flag = True

    # get the neigbor coordinates which is the vertex within the circle (around POI, radius t*ks)
    neighbor = neigh[np.argmin(dist),:]
    neighbor_index = np.argmin(dist)

    if return_index:
        return neighbor, neighbor_index
    else:
        return neighbor


'''
Created on 8.4.2017

@author: Markus.Walden
'''
from sklearn.preprocessing import StandardScaler
import scipy.optimize as opt
from numpy import cosh, zeros_like, mgrid, zeros
import matplotlib.pyplot as plt

from sympy import (
     symbols,   # define symbols for symbolic math
     diff,      # differentiate expressions
     integrate, # integrate expressions
     Rational,  # define rational numbers
     lambdify,  # turn symbolic expr. into Python functions
     solve,
     exp,
     sin,
     cos,
     latex
     )

class SolverDiff(object):
    '''
    classdocs
    http://hplgit.github.io/primer.html/doc/pub/formulas/._formulas-bootstrap007.html#sec:formula:sympy
    '''
    def __init__(self, params):
        '''
        Constructor
        '''

    @staticmethod
    def solverUsingDiff():
        '''
        Test method for sympy, test equation roots:
        - y = v0 * t - Rational(1,2) * g * t ** 2

        :return: symbolic equation for root(s)
        :rtype: symbolic
        '''
        t, v0, g = symbols('t v0 g')
        y = v0 * t - Rational(1,2) * g * t ** 2

        roots = solve(y,t)
        print (y.subs(t, roots[0]))
        return roots

    @staticmethod
    def taylorSeries():
        '''
        Test method for sympy, test equation taylor series:
        - y = v0 * t - Rational(1,2) * g * t ** 2

        :return: symbolic equation for taylor series(s)
        :rtype: symbolic
        '''
        t, v0, g = symbols('t v0 g')
        y = v0 * t - Rational(1,2) * g * t ** 2

        f = exp(t)
        o = f.series(t, 0, 3)
        print (latex(o))

        f = exp(sin(t))
        o = f.series(t, 0, 8)
        print (latex(o))

        return f

    @staticmethod
    def symbolicMathTest():
        '''
        ValueError:
            specify dummy variables for -g*t + v0.
            If the integrand contains more than one free symbol, an integration variable should be supplied explicitly e.g.,
            integrate(f(x, y), x)

        :return: t, v0, g, dydt
        :rtype: time, initial, internal variable, time differential
        '''
        t, v0, g = symbols('t v0 g')
        y = v0 * t - Rational(1,2) * g * t ** 2
        try:
            dydt = diff(y,t)
            dydt2 = diff(y, t, t)  # 2nd derivative
            y2 = integrate(y, t)
            print ('y: ', y)
            print ('derivative of: dy/dt: ', dydt)
            print ('second derivative: ', dydt2)
            print ('y integral: ', y2)
        except ValueError:
            print ('bugi')
        return t, v0, g, dydt

def main ():
    '''
    newton_krylov
    '''
    t, v0, g, dydt = SolverDiff.symbolicMathTest()
    v = lambdify([t, v0, g], dydt)

    roots = SolverDiff.solverUsingDiff()
    f = SolverDiff.taylorSeries()

    print (roots)

#    solver()
#    pq = PredictSVMOnQuandl()
#    pq.populateCompanyData(readClassifier = True, runList = False)
#    pq.df_main
#    pq.df_classifier

    return None
    #X_std = StandardScaler().fit_transform(X)

def solver():
    nx, ny = 75, 75
    hx, hy = 1./(nx-1), 1./(ny-1)

    P_left, P_right = 0, 0
    P_top, P_bottom = 1, 0

    def residual(P):
        d2x = zeros_like(P)
        d2y = zeros_like(P)

        d2x[1:-1] = (P[2:]   - 2*P[1:-1] + P[:-2]) / hx/hx
        d2x[0]    = (P[1]    - 2*P[0]    + P_left)/hx/hx
        d2x[-1]   = (P_right - 2*P[-1]   + P[-2])/hx/hx

        d2y[:,1:-1] = (P[:,2:] - 2*P[:,1:-1] + P[:,:-2])/hy/hy
        d2y[:,0]    = (P[:,1]  - 2*P[:,0]    + P_bottom)/hy/hy
        d2y[:,-1]   = (P_top   - 2*P[:,-1]   + P[:,-2])/hy/hy

        return d2x + d2y - 10*cosh(P).mean()**2

    guess = zeros((nx, ny), float)
    sol = opt.newton_krylov(residual, guess, method='lgmres', verbose=1)
    print('Residual: %g' % abs(residual(sol)).max())

    x, y = mgrid[0:1:(nx*1j), 0:1:(ny*1j)]
    plt.pcolor(x, y, sol)
    plt.colorbar()
    plt.show()

if __name__ == '__main__':
    main()


''' mbinary
#########################################################################
# File : solve-linear-by-iteration.py
# Author: mbinary
# Mail: zhuheqin1@gmail.com
# Blog: https://mbinary.xyz
# Github: https://github.com/mbinary
# Created Time: 2018-10-02  21:14
# Description:
#########################################################################
'''

'''
#########################################################################
# File : solve-linear-by-iteration.py
# Author: mbinary
# Mail: zhuheqin1@gmail.com
# Blog: https://mbinary.xyz
# Github: https://github.com/mbinary
# Created Time: 2018-05-04  07:42
# Description:
#########################################################################
'''

import numpy as np
from operator import le, lt
def jacob(A, b, x, accuracy=None, times=6):
    ''' Ax=b,  arg x is the init val, times is the time of iterating'''
    A, b, x = np.matrix(A), np.matrix(b), np.matrix(x)
    n, m = A.shape
    if n != m:
        raise Exception("Not square matrix: {A}".format(A=A))
    if b.shape != (n, 1):
        raise Exception(
            'Error: {b} must be {n} x1 in dimension'.format(b=b, n=n))
    D = np.diag(np.diag(A))
    DI = np.zeros([n, n])
    for i in range(n):
        DI[i, i] = 1/D[i, i]
    R = np.eye(n) - DI * A
    g = DI * b
    print('R =\n{}'.format(R))
    print('g =\n{}'.format(g))
    last = -x
    if accuracy != None:
        ct = 0
        while 1:
            ct += 1
            tmp = x-last
            last = x
            mx = max(abs(i) for i in tmp)
            if mx < accuracy:
                return x
            x = R*x+g
            print('x{ct} =\n{x}'.format(ct=ct, x=x))
    else:
        for i in range(times):
            x = R*x+g
            print('x{ct} =  \n{x}'.format(ct=i+1, x=x))
    print('isLimitd: {}'.format(isLimited(A)))
    return x

def gauss_seidel(A, b, x, accuracy=None, times=6):
    ''' Ax=b,  arg x is the init val, times is the time of iterating'''
    A, b, x = np.matrix(A), np.matrix(b), np.matrix(x)
    n, m = A.shape
    if n != m:
        raise Exception("Not square matrix: {A}".format(A=A))
    if b.shape != (n, 1):
        raise Exception(
            'Error: {b} must be {n} x1 in dimension'.format(b=b, n=n))
    D = np. matrix(np.diag(np.diag(A)))
    L = np.tril(A) - D  # L = np.triu(D.T) - D
    U = np.triu(A) - D
    DLI = (D+L).I
    S = - (DLI) * U
    f = (DLI)*b
    print('S =\n{}'.format(S))
    print('f =\n{}'.format(f))
    last = -x
    if accuracy != None:
        ct = 0
        while 1:
            ct += 1
            tmp = x-last
            last = x
            mx = max(abs(i) for i in tmp)
            if mx < accuracy:
                return x
            x = S*x+f
            print('x{ct} =\n{x}'.format(ct=ct, x=x))
    else:
        for i in range(times):
            x = S*x+f
            print('x{ct} =  \n{x}'.format(ct=i+1, x=x))
    print('isLimitd: {}'.format(isLimited(A)))
    return x

def isLimited(A, strict=False):
    '''通过检查A是否是[严格]对角优来判断迭代是否收敛, 即对角线上的值是否都大于对应行(或者列)的值'''
    diag = np.diag(A)
    op = lt if strict else le
    if op(A.max(axis=0), diag).all():
        return True
    if op(A.max(axis=1), diag).all():
        return True
    return False

testcase = []

def test():
    for func, A, b, x, *args in testcase:
        acc = None
        times = 6
        if args != []:
            if isinstance(args[0], int):
                times = args[0]
            else:
                acc = args[0]
        return func(A, b, x, acc, times)

if __name__ == '__main__':
    A = [[2, -1, -1],
         [1, 5, -1],
         [1, 1, 10]
         ]
    b = [[-5], [8], [11]]
    x = [[1], [1], [1]]
    # testcase.append([gauss_seidel,A,b,x])

    A = [[2, -1, 1], [3, 3, 9], [3, 3, 5]]
    b = [[-1], [0], [4]]
    x = [[0], [0], [0]]
    # testcase.append([jacob,A,b,x])

    A = [[5, -1, -1],
         [3, 6, 2],
         [1, -1, 2]
         ]
    b = [[16], [11], [-2]]
    x = [[1], [1], [-1]]
    testcase.append([gauss_seidel, A, b, x, 0.001])
    test()


import numpy as np

# common loss class
class Loss:

    # calculates the data and regularization losses
    # given model output and ground truth values
    def calculate(self, output, y):
        # calculate sample losses
        sample_losses = self.forward(output, y)

        # calculate mean loss
        data_loss = np.mean(sample_losses)

        # return loss
        return data_loss

# cross-entropy loss
class Loss_CategoricalCrossentropy(Loss):

    # forward pass
    def forward(self, y_pred, y_true):
        # number of samples in a batch
        samples = len(y_pred)

        # clip data to prevent division by 0
        # clip both sides to not drag mean towards any value
        y_pred_clipped = np.clip(y_pred, 1e-7, 1-1e-7)

        # probabilities for target values -
        # only if categorical labels
        if len(y_true.shape) == 1:
            correct_confidences = y_pred_clipped[
                range(samples), y_true]

        # make values - only for one-hot encoded labels
        elif len(y_true.shape) == 2:
            correct_confidences = np.sum(
                y_pred_clipped * y_true, axis=1)

        # losses
        negative_log_likelihoods = -np.log(correct_confidences)

        return negative_log_likelihoods

    # backward pass
    def backward(self, dvalues, y_true):
        # number of samples
        samples = len(dvalues)

        # number of labels in every sample
        # we'll use the first sample to count them
        labels = len(dvalues[0])

        # if labels are sparse, turn them into one-hot vector
        if len(y_true.shape) == 1:
            y_true = np.eye(labels)[y_true]

        # calculate gradient
        self.dinputs = -y_true / dvalues

        # normalize gradient
        self.dinputs = self.dinputs / samples


import numpy as np
import matplotlib.pyplot as plt
from sklearn import metrics
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix

'''
Multiclass Logistic Regression and Probit Regression solely based on numpy.
Uses GD for training.
Author: Hanbaek Lyu (5/3/2021)
'''

def fit_MLR_GD(Y, H, W0=None, sub_iter=100, stopping_diff=0.01):
        '''
        Convex optimization algorithm for Multiclass Logistic Regression using Gradient Descent
        Y = (n x k), H = (p x n) (\Phi in lecture note), W = (p x k)
        Multiclass Logistic Regression: Y ~ vector of discrete RVs with PMF = sigmoid(H.T @ W)
        MLE -->
        Find \hat{W} = argmin_W ( sum_j ( log(1+exp(H_j.T @ W) ) - Y.T @ H.T @ W ) )
        '''
        k = Y.shape[1] # number of classes
        if W0 is None:
            W0 = np.random.rand(H.shape[0],k) #If initial coefficients W0 is None, randomly initialize

        W1 = W0.copy()
        i = 0
        grad = np.ones(W0.shape)
        while (i < sub_iter) and (np.linalg.norm(grad) > stopping_diff):
            Q = 1/(1+np.exp(-H.T @ W1))  # probability matrix, same shape as Y
            # grad = H @ (Q - Y).T + alpha * np.ones(W0.shape[1])
            grad = H @ (Q - Y)
            W1 = W1 - (np.log(i+1) / (((i + 1) ** (0.5)))) * grad
            i = i + 1
            # print('iter %i, grad_norm %f' %(i, np.linalg.norm(grad)))
        return W1

def fit_PR_GD(Y, H, W0=None, sub_iter=100, stopping_diff=0.01):
        '''
        Convex optimization algorithm for Probit Regression using Gradient Descent
        Y = (n x 1), H = (p x n) (\Phi in lecture note), W = (p x 1)
        Logistic Regression: Y ~ Bernoulli(Q), Q = Probit(H.T @ W)
        '''
        if W0 is None:
            W0 = 1-2*np.random.rand(H.shape[0],1) #If initial coefficients W0 is None, randomly initialize from [-1,1]

        W1 = W0.copy()
        i = 0
        grad = np.ones(W0.shape)
        while (i < sub_iter) and (np.linalg.norm(grad) > stopping_diff):
            Q = norm.pdf(H.T @ W1) * ( (1-Y)/norm.cdf(-H.T @ W1) - Y/norm.cdf(H.T @ W1) )
            grad = H @ Q
            W1 = W1 - (np.log(i+1) / (((i + 1) ** (0.5)))) * grad
            i = i + 1
            # print('iter %i, grad_norm %f' %(i, np.linalg.norm(grad)))
        return W1

def compute_accuracy_metrics(Y_test, P_pred, use_opt_threshold=False, verbose=False):
    # y_test = binary label
    # P_pred = predicted probability for y_test
    # compuate various binary classification accuracy metrics
    fpr, tpr, thresholds = metrics.roc_curve(Y_test, P_pred, pos_label=None)
    mythre = thresholds[np.argmax(tpr - fpr)]
    myauc = metrics.auc(fpr, tpr)
    # print('!!! auc', myauc)

    # Compute classification statistics
    threshold = 0.5
    if use_opt_threshold:
        threshold = mythre

    Y_pred = P_pred.copy()
    Y_pred[Y_pred < threshold] = 0
    Y_pred[Y_pred >= threshold] = 1

    mcm = confusion_matrix(Y_test, Y_pred)
    tn = mcm[0, 0]
    tp = mcm[1, 1]
    fn = mcm[1, 0]
    fp = mcm[0, 1]

    accuracy = (tp + tn) / (tp + tn + fp + fn)
    sensitivity = tn / (tn + fp)
    specificity = tp / (tp + fn)
    precision = tp / (tp + fp)
    fall_out = fp / (fp + tn)
    miss_rate = fn / (fn + tp)

    # Save results
    results_dict = {}
    results_dict.update({'Y_test': Y_test})
    results_dict.update({'Y_pred': Y_pred})
    results_dict.update({'AUC': myauc})
    results_dict.update({'Opt_threshold': mythre})
    results_dict.update({'Accuracy': accuracy})
    results_dict.update({'Sensitivity': sensitivity})
    results_dict.update({'Specificity': specificity})
    results_dict.update({'Precision': precision})
    results_dict.update({'Fall_out': fall_out})
    results_dict.update({'Miss_rate': miss_rate})

    if verbose:
        for key in [key for key in results_dict.keys()]:
            print('% s ===> %.3f' % (key, results_dict.get(key)))
    return results_dict

def multiclass_accuracy_metrics(Y_test, P_pred, class_labels=None, use_opt_threshold=False):
    # y_test = multiclass one-hot encoding  labels
    # Q = predicted probability for y_test
    # compuate various classification accuracy metrics
    results_dict = {}
    y_test = []
    y_pred = []
    for i in np.arange(Y_test.shape[0]):
        for j in np.arange(Y_test.shape[1]):
            if Y_test[i,j] == 1:
                y_test.append(j)
            if P_pred[i,j] == np.max(P_pred[i,:]):
                # print('!!!', np.where(P_pred[i,:]==np.max(P_pred[i,:])))
                y_pred.append(j)

    confusion_mx = metrics.confusion_matrix(y_test, y_pred)
    print('!!! confusion_mx', confusion_mx)
    results_dict.update({'confusion_mx':confusion_mx})

    return results_dict


# Exercise 5
# Compute an approximate value for π by using Monte-Carlo simulation to approximate the ratio between the
# area of a circle of radius 1 to the area of the square circumscribed to it (which has side length equal to 2).
# 1. Set a stopping rule in terms of the confidence interval for the success probability (where a success occurs if
# a point falls within the circle).
# Make an algorithm that keeps drawing additional points until the stopping rule is satisfied.

# import
import numpy as np
from matplotlib import pyplot as plt
from numpy import mean, min, max, median, quantile
from scipy.stats import binom, norm, t as student, expon, poisson, chi2
from math import sqrt, pow, floor, ceil, exp, pi

# define corrected functions for std and variance

def std(values):
	return np.std(values, ddof=1)

outcomes = []
points_inside = []
points_outside = []
# generate some data first
i = 0
while i <= 100:
	i += 1
	x = np.random.rand() * 2 - 1
	y = np.random.rand() * 2 - 1
	d = sqrt(x**2 + y**2)
	if d <= 1:
		outcomes.append(1)
		points_inside.append([x, y])
	else:
		outcomes.append(0)
		points_outside.append([x, y])

l = 0.02
gamma = 0.95
z = norm.ppf((1 + gamma) / 2)
while (True):
	S = std(outcomes)
	interval_len = 2 * z * S / sqrt(len(outcomes))
	if interval_len < l:
		break

	i += 1
	x = np.random.rand() * 2 - 1
	y = np.random.rand() * 2 - 1
	d = sqrt(x**2 + y**2)
	if d <= 1:
		outcomes.append(1)
		points_inside.append([x, y])
	else:
		outcomes.append(0)
		points_outside.append([x, y])

points_inside = np.array(points_inside)
points_outside = np.array(points_outside)
plt.scatter(points_inside[:, 0], points_inside[:, 1], s=0.2)
plt.scatter(points_outside[:, 0], points_outside[:, 1], s=0.2)
plt.axis('equal')
plt.show()

print(f'Estimated π value: {mean(outcomes) * 4: .4f}, using {i} points')
print(f'Estimated probability: {mean(outcomes): .4f}, with CI at 0.95 of size {interval_len: .4f}, compared to the theoretical probability {pi/4: .4f}')


import matplotlib.pyplot as plt
import numpy as np
import math

# Problem1

def SPC2(F, C):
    l = len(F)
    A = np.empty(l)
    for i in xrange(l):
        v1 = F[i, 1]-F[i, 0]
        v2 = F[i, 2]-F[i, 0]
        A[i] = .5*np.abs(v1[0]*v2[1]-v1[1]*v2[0])
    b = np.empty(3)
    total = np.sum(A)
    b = np.dot(A, C)/total
    return b

# Problem2

def Prob2(omega, psi, r=3):
    t = r*math.sin(psi)
    return np.array([t*math.cos(omega),
                    t*math.sin(omega),
                    r*math.cos(psi)])

# Problem3

def Transform(c):
    cnorm = np.linalg.norm(c)
    t = np.arccos(-c[2]/cnorm)
    wstar = np.array([c[1]/cnorm, -c[0]/cnorm, 0])
    w = wstar/np.linalg.norm(wstar)
    what = np.array([[0, -w[2], w[1]], [w[2], 0, -w[0]], [-w[1], w[0], 0]])
    R = np.eye(3)+np.sin(t)*what+(1-np.cos(t))*np.linalg.matrix_power(what, 2)
    P = np.zeros((4, 4))
    P[:3, :3] = R.T
    P[:3, -1] = np.dot(-R.T, c)
    P[-1, -1] = 1
    return P

# Problem4

def Project(P, Fs, f=.5):
    l = len(Fs)
    F = np.ones((l, 4, 3))
    F[:, :-1, :] = Fs

    def Pc(y, f=.5):
        return np.array([y[0]/(f*y[2]), y[1]/(f*y[2])])

    Q = np.empty((l, 3, 2))
    for i in xrange(l):
        Q[i] = Pc(np.dot(P, F[i, :, :])).T
    return Q

# Problem5

def Visible(F, c, r=1):
    centers = np.mean(F[:, :, :], axis=-1)
    e = np.sqrt(np.sum((c-centers)**2, axis=1))<np.sqrt(np.sum((c)**2)+r**2)-.2
    return e

# Problem6

def SPC(F, C, omega, psi, r=3):
    c = Prob2(omega, psi, r=3)
    P = Transform(c)
    Q = Project(P, F, f=.5)
    l = len(F)
    A = np.empty(l)
    for i in xrange(l):
        v1 = Q[i,1] - Q[i,0]
        v2 = Q[i,2] - Q[i,0]
        A[i] = .5*abs(v1[0]*v2[1]-v1[1]*v2[0])
    M = Visible(F, c, r=1)*A
    return M.dot(C)/M.sum()


import numpy as np

def Sigmoid(z):
	#return 1.0/(1.0 + np.exp(-z))
	return np.exp(-np.logaddexp(0,-z))

def SigmoidGradient(z):

	return Sigmoid(z)*(1.0-Sigmoid(z));

def InverseSigmoid(a):
	z = - np.log(1/np.float64(a) - 1)
	return z

def InverseSigmoidGradient(a):
	z = InverseSigmoid(a)
	return SigmoidGradient(z)


# Matrices and Matrix Operations
#----------------------------------
#
# This function introduces various ways to create
# matrices and how to use them in TensorFlow

import numpy as np
import tensorflow as tf
from tensorflow.python.framework import ops
ops.reset_default_graph()

# Declaring matrices
sess = tf.Session()

# Declaring matrices

# Identity matrix
identity_matrix = tf.diag([1.0,1.0,1.0])
print(sess.run(identity_matrix))

# 2x3 random norm matrix
A = tf.truncated_normal([2,3])
print(sess.run(A))

# 2x3 constant matrix
B = tf.fill([2,3], 5.0)
print(sess.run(B))

# 3x2 random uniform matrix
C = tf.random_uniform([3,2])
print(sess.run(C))  # Note that we are reinitializing, hence the new random variables

# Create matrix from np array
D = tf.convert_to_tensor(np.array([[1., 2., 3.], [-3., -7., -1.], [0., 5., -2.]]))
print(sess.run(D))

# Matrix addition/subtraction
print(sess.run(A+B))
print(sess.run(B-B))

# Matrix Multiplication
print(sess.run(tf.matmul(B, identity_matrix)))

# Matrix Transpose
print(sess.run(tf.transpose(C))) # Again, new random variables

# Matrix Determinant
print(sess.run(tf.matrix_determinant(D)))

# Matrix Inverse
print(sess.run(tf.matrix_inverse(D)))

# Cholesky Decomposition
print(sess.run(tf.cholesky(identity_matrix)))

# Eigenvalues and Eigenvectors
print(sess.run(tf.self_adjoint_eig(D)))


from sympy import sqrt, simplify, symbols, integrate, solve, Matrix, eye, cbrt
import numpy as np
from numpy.linalg import inv

def norma(x):
    """Devuelve la norma de un vector <x,x>.

    Args:
        x (vector): Vector fila o columna

    Returns:
        float: Norma del vector
    """

    if x.shape[0] == 1:
        return sqrt((x * x.T)[0])
    else:
        return sqrt((x.T * x)[0])

def print_verbose(msg, verbose):
    """Printea el contenido de msg si verbose.

    Args:
        msg (str, list): mensaje a describir. Puede ser una list ade strings.
        verbose (bool): verbose
    """

    if type(msg) == str:
        msg = [msg]
    if verbose:
        for i in msg:
            print(i)

def norma_1(A, verbose=False):
    """Devuelve la norma 1, ||A||_1 de una matriz. (La suma de la columna con mayor suma).

    Args:
        A (matriz): Matriz
        verbose (bool, optional): Imprime cosas. Defaults to False.
    """
    norma_1 = max(np.sum(abs(A), axis=0))
    print_verbose(f"La norma 1 es: {norma_1}.", verbose)  # Mayor columna
    return norma_1

def norma_inf(A, verbose=False):
    """Devuelve la norma 1, ||A||_1 de una matriz. (La suma de la fila con mayor suma).

    Args:
        A (matriz): Matriz
        verbose (bool, optional): Imprime cosas. Defaults to False.
    """
    norma_inf = max(np.sum(abs(A), axis=1))
    print_verbose(f"La norma inf es: {norma_inf}.", verbose)  # Mayor fila
    return norma_inf

def norma_2(A, verbose=False):
    """Devuelve la norma 1, ||A||_1 de una matriz. Se define como la raíz del mayor de los autovalores de A*A.T.

    Args:
        A (matriz): Matriz
        verbose (bool, optional): Imprime cosas. Defaults to False.
    """
    eigs = sqrt([simplify(i) for i in list((A.T * A).eigenvals().keys())])
    norma_2 = np.max(eigs)
    print_verbose(f"Los eigs de A.T * A (sqrt) son: {eigs}", verbose)
    print_verbose(f"La norma 2 es: {norma_2}", verbose)
    return norma_2

x = symbols('x')

def norma_p_func(expr, var=x, p=2, a=0, b=1):
    """Calcula la norma p de una función

    Args:
        expr (expr): funcion
        var (variable, optional): variable de la funcion. Defaults to x.
        p (int, optional): p. Defaults to 2.
        a (int, optional): limite inferior. Defaults to 0.
        b (int, optional): limite superior. Defaults to 1.

    Returns:
        expr: expresión de la norma
    """
    exp_p = abs(expr ** p) if p % 2 == 1 else expr ** p

    if a is None or b is None:
        integral = integrate((expr) ** p, var)
    else:
        integral = integrate((expr) ** p, (var, a, b))

    if p == 2:
        integral = simplify(sqrt(integral))
    elif p == 3:
        integral = simplify(cbrt(integral))
    else:
        integral = simplify(integral ** (1 / p))

    return integral

def norma_inf_func(expr, var=x, a=None, b=None, return_sols=False):
    """Calcula la norma infinita de una función

    Args:
        expr (expr): funcion
        var (variable, optional): variable de la funcion. Defaults to x.
        a (int, optional): limite inferior. Defaults to 0.
        b (int, optional): limite superior. Defaults to 1.
        return_sols (bool, optional): si True, devuelve las soluciones y la localizacion

    Returns:
        expr: expresión de la norma
    """
    fprime = expr.diff(var)

    solutions = [(xx, abs(expr.subs(var, xx))) for xx in solve(fprime, var)]
    if a is None or b is None:
        pass
    else:
        solutions = [i for i in solutions if (a <= i[0]) & (b >= i[0])]

    max_val = max([i[1] for i in solutions])

    return max_val, solutions

def matriz_inversa(M, thresh=0.1):
    """
    Calcula la inversa de manera natural con sympy. Si no se satisface que M**(-1) * M = I (mediante la norma de M**(-1) * M - I), entonces calculamos la inversa con numpy.
    Aviso! Que la inversa nueva tenga menor norma que la original, no significa que sea lo suficientemente aceptable como para poder efectuar cálculos con ella.

    Args:
        M (Matriz): Matriz para calcular la inversa
        thresh (float, optional): Valor límite de la norma de M.inv * M - I. Defaults to 0.1.

    Returns:
        Matriz: Matriz inversa
    """

    M_inv = M.inv()

    norma = (simplify(M_inv * M) - eye(M.shape[0])).norm()

    if norma > thresh:
        print('AVISO! La inversa es inestable.')
        M_inv_np = Matrix(inv(np.array(M, dtype=float)))
        norma_numpy = (simplify(M_inv_np * M) - eye(M.shape[0])).norm()

        if norma_numpy < norma:
            print('Devolvemos la inversa usando numpy.')
            return M_inv_np

    return M_inv


"""
The Adam algorithm improves the SGD update with two concepts: adaptive vector stepsizes and momentum.
It keeps running estimates of the mean and second moment of the updates, denoted by:

m_t = B_m * m_t-1 + (1 - B_m)g1_t
v_t = B_v * v_t-1 + (1 - B_v)g^2_t

B_m, B_v are fixed parameters
The g's are the gradients
Given that m, v are init to 0, they are biased towards 0. To get unbiased estimates,
we use...
\hat{m_t} = m_t / (1 - B^t_m)
\hat{v_t} = v_t / (1 - B^t_v)

NOTE: in the above calculations, the beta parameters are raised to the power of the timestep

Using the above, the weight updates are ultimately...
w_t = w_t-1 + (alpha / sqrt(\hat{v_t} + epsilon)) * \hat{m_t}
"""

import numpy as np

class ADAM:
    """
    This is a bare-bones implementation of the ADAM optimizer based on Kingma, Ba (2014) https://arxiv.org/abs/1412.6980

    Attributes:
        layer_dims (list): should be directly passed from the NN.layer_dims attribute to ensure compatibility
        alpha (float): the step-size (learning rate)
        beta_m (float): the coefficient for the "momentum" hyperparameter
        beta_v (float): the coefficient for the "velocity" hyperparameter
        epsilon (float): a very small value, meant to be a stabilizer for when v_hat may be 0
    """
    def __init__(self, layer_dims, alpha, beta_m, beta_v, epsilon):
        self.layer_dims = layer_dims
        self.alpha = alpha
        self.beta_m = beta_m
        self.beta_v = beta_v
        self.epsilon = epsilon

        self.m = [dict() for _ in range(len(self.layer_dims) - 1)]
        self.v = [dict() for _ in range(len(self.layer_dims) - 1)]

        for i in range(len(self.layer_dims) - 1):
            self.m[i]['W'] = np.zeros((self.layer_dims[i], self.layer_dims[i + 1]))
            self.m[i]['b'] = np.zeros((1, self.layer_dims[i + 1]))
            self.v[i]['W'] = np.zeros((self.layer_dims[i], self.layer_dims[i + 1]))
            self.v[i]['b'] = np.zeros((1, self.layer_dims[i + 1]))

        # Instead of requiring a global tracker of the step, we can keep track of the product
        self.beta_m_exp = self.beta_m
        self.beta_v_exp = self.beta_v

    def update_weights(self, weights, gradient):
        """The completion of the backpropagation algorithm, applying both the momentum and the gradient to the weights.
        In addition, this method handles the updating of all hyperparameters.

        Args:
            weights (array of dicts): the weight dicts from the network class, use a DIRECT REFERENCE! NOT A COPY!
            gradient (array of dicts): the array of dicts returned by the NN.get_gradient() method

        Returns:
            None. the network's weights are mutable, so this method will actually be mutating the weights of the
            network directly, assuming a direct reference to them was passed to the "weights" argument
        """
        for i in range(len(weights)):
            for param in weights[i].keys():
                self.m[i][param] = self.beta_m * self.m[i][param] + (1 - self.beta_m) * gradient[i][param]
                self.v[i][param] = self.beta_v * self.v[i][param] + (1 - self.beta_v) * gradient[i][param] ** 2

                m_hat = self.m[i][param] / (1 - self.beta_m_exp)
                v_hat = self.v[i][param] / (1 - self.beta_v_exp)

                weights[i][param] = weights[i][param] - self.alpha / (np.sqrt(v_hat) + self.epsilon) * m_hat

        self.beta_m_exp *= self.beta_m
        self.beta_v_exp *= self.beta_v


import math
import numpy as np

def evaluate(job_id, params):

    x = params['X']
    y = params['Y']

    #print 'Evaluating at (%f, %f)' % (x, y)

    # if x < 0 or x > 5.0 or y > 5.0:
    #     return np.nan
    # Feasible region: x in [0,5] and y in [0,5]

    obj1 = 30*(x**4 + y**4)
    obj2 = 30*(x**2 + y**2)
    obj3 = griewank(x, y)
    obj4 = gramacy(x, y)

    return {
        "parabola6"       : obj1,
        "parabola2"       : obj2,
        "griewank"       : obj3,
        "gramacy"        : obj4,
    }

def gramacy(x,y):
    x = scale_from_unity(x, -2, 6)
    y = scale_from_unity(y, -2, 6)
    return x*np.exp(-x**2 -y**2)

def griewank(x,y):
    x = scale_from_unity(x, -600, 600)
    y = scale_from_unity(y, -600, 600)
    return (x**2 + y**2)/4000 - np.cos(x)*np.cos(y/np.sqrt(2))+1

def branin(x,y):
    x = scale_from_unity(x, -10, 10)
    y = scale_from_unity(y, -10, 10)

    return float(np.square(y - (5.1/(4*np.square(math.pi)))*np.square(x) + (5/math.pi)*x- 6) + 10*(1-(1./(8*math.pi)))*np.cos(x) + 10)

def levy(x,y):
    x = scale_from_unity(x, -5, 10)
    y = scale_from_unity(y, 0, 15)
    w1 = (x-1)/4 + 1
    w2 = (y-1)/4 + 1
    return np.square(np.sin(np.pi*w1)) + (w2-1)**2*(1 + np.sin(2* np.pi*w2)**2)

scale = lambda x, a, b, a2, b2: (x-a)*(b2-a2)/(b-a)+a2
def scale_from_unity(x, a, b):
    return scale(x, -1,1,a,b)

def main(job_id, params):
    try:
        return evaluate(job_id, params)
    except Exception as ex:
        print ex
        print 'An error occurred in parabolic.py'
        return np.nan


import sympy as sp, math as m

x = sp.symbols('x')
sp.init_printing(use_unicode=True)

#f=sp.Lambda(x, x*sp.cos(x)-x**2*sp.sin(x))
#f=sp.Lambda(x, sp.log(x+2)/(x**2+1))
#f=sp.Lambda(x, x**2*sp.exp(x))

f=sp.Lambda(x, sp.exp(sp.sin(x)**2-2))

"""
def f(x):
    #return x*m.cos(x)-x**2*m.sin(x)
    #return m.exp(3*x)*m.sin(3*x)/(x**4+1)
    return x**3*m.cos(x**2+3)
"""

def simpson_compuesta(f, a, b, n):

    m = 2*n

    h = (b-a)/m

    result = sp.N(f(a)+f(b))

    for i in range(1, n+1):
        result += sp.N(4*f(a+(2*i-1)*h))

    for i in range(2, n+1):
        result += sp.N(2*f(a+(2*i-2)*h))

    result *= h/3

    return result

a, b = 0, 2
n = 54

result = simpson_compuesta(f, a, b, n)

print(result)

# Error

h = (b-a)/(2*n)

cota_4=7.3576

print("Cota error: "+str(abs((b-a)*h**4/180*cota_4)))

print("Error: "+str(abs(result-simpson_compuesta(f,a,b,500))))

# Ej 14
"""
def fa(x):
    return x**2*m.log(x)

def fb(x):
    return x**3*m.exp(-x)

def fc(x):
    return 3*x/(x**2-4)

def fd(x):
    return m.cos(x)*m.exp(3*x)

print("a) " + str(simpson_compuesta(fa, 1, 1.5, 50)))
print("b) " + str(simpson_compuesta(fb, 0, 1, 50)))
print("c) " + str(simpson_compuesta(fc, 1, 1.8, 50)))
print("d) " + str(simpson_compuesta(fd, 0, m.pi/4, 50)))
"""


"""Calculate where null points of an Airy pattern lie."""
from scipy.special import j1
import matplotlib.pyplot as plt
import numpy as np
import os

from convenience import plot_aa_style, rel_path

STEPSIZE = 1e-6
PLOT = True

x_range = np.arange(0, 50, STEPSIZE)
y_range = 4*(j1(x_range)/x_range)**2
nulls = []

# Find where curve changes direction
ind = np.diff(np.sign(np.diff(y_range)))
x_null = x_range[1:-1][ind > 0]
y_null = y_range[1:-1][ind > 0]

print(x_null)

if PLOT:
    title = r"Bessel function over $\text{x}$"

    plot_aa_style()

    fig = plt.figure()
    ax = fig.add_subplot(111)
    plt.title(title)
    plt.plot(x_range[::10], y_range[::10])
    plt.scatter(x_null, y_null)
    plt.yscale('log')
    plt.tight_layout()

    plt.savefig(rel_path('./plots/null_sidelobes.pdf'))


import os
import sys
import numpy as np
from scipy.signal import get_window
import matplotlib.pyplot as plt
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../software/models/'))
import stft
from loadTestCases import *
import utilFunctions as UF

eps = np.finfo(float).eps

"""
A4-Part-3: Computing band-wise energy envelopes of a signal

Write a function that computes band-wise energy envelopes of a given audio signal by using the STFT.
Consider two frequency bands for this question, low and high. The low frequency band is the set of
all the frequencies between 0 and 3000 Hz and the high frequency band is the set of all the
frequencies between 3000 and 10000 Hz (excluding the boundary frequencies in both the cases).
At a given frame, the value of the energy envelope of a band can be computed as the sum of squared
values of all the frequency coefficients in that band. Compute the energy envelopes in decibels.

Refer to "A4-STFT.pdf" document for further details on computing bandwise energy.

The input arguments to the function are the wav file name including the path (inputFile), window
type (window), window length (M), FFT size (N) and hop size (H). The function should return a numpy
array with two columns, where the first column is the energy envelope of the low frequency band and
the second column is that of the high frequency band.

Use stft.stftAnal() to obtain the STFT magnitude spectrum for all the audio frames. Then compute two
energy values for each frequency band specified. While calculating frequency bins for each frequency
band, consider only the bins that are within the specified frequency range. For example, for the low
frequency band consider only the bins with frequency > 0 Hz and < 3000 Hz (you can use np.where() to
find those bin indexes). This way we also remove the DC offset in the signal in energy envelope
computation. The frequency corresponding to the bin index k can be computed as k*fs/N, where fs is
the sampling rate of the signal.

To get a better understanding of the energy envelope and its characteristics you can plot the envelopes
together with the spectrogram of the signal. You can use matplotlib plotting library for this purpose.
To visualize the spectrogram of a signal, a good option is to use colormesh. You can reuse the code in
sms-tools/lectures/4-STFT/plots-code/spectrogram.py. Either overlay the envelopes on the spectrogram
or plot them in a different subplot. Make sure you use the same range of the x-axis for both the
spectrogram and the energy envelopes.

NOTE: Running these test cases might take a few seconds depending on your hardware.

Test case 1: Use piano.wav file with window = 'blackman', M = 513, N = 1024 and H = 128 as input.
The bin indexes of the low frequency band span from 1 to 69 (69 samples) and of the high frequency
band span from 70 to 232 (163 samples). To numerically compare your output, use loadTestCases.py
script to obtain the expected output.

Test case 2: Use piano.wav file with window = 'blackman', M = 2047, N = 4096 and H = 128 as input.
The bin indexes of the low frequency band span from 1 to 278 (278 samples) and of the high frequency
band span from 279 to 928 (650 samples). To numerically compare your output, use loadTestCases.py
script to obtain the expected output.

Test case 3: Use sax-phrase-short.wav file with window = 'hamming', M = 513, N = 2048 and H = 256 as
input. The bin indexes of the low frequency band span from 1 to 139 (139 samples) and of the high
frequency band span from 140 to 464 (325 samples). To numerically compare your output, use
loadTestCases.py script to obtain the expected output.

In addition to comparing results with the expected output, you can also plot your output for these
test cases.You can clearly notice the sharp attacks and decay of the piano notes for test case 1
(See figure in the accompanying pdf). You can compare this with the output from test case 2 that
uses a larger window. You can infer the influence of window size on sharpness of the note attacks
and discuss it on the forums.
"""
def computeEngEnv(inputFile, window, M, N, H):
    """
    Inputs:
            inputFile (string): input sound file (monophonic with sampling rate of 44100)
            window (string): analysis window type (choice of rectangular, triangular, hanning,
                hamming, blackman, blackmanharris)
            M (integer): analysis window size (odd positive integer)
            N (integer): FFT size (power of 2, such that N > M)
            H (integer): hop size for the stft computation
    Output:
            The function should return a numpy array engEnv with shape Kx2, K = Number of frames
            containing energy envelop of the signal in decibles (dB) scale
            engEnv[:,0]: Energy envelope in band 0 < f < 3000 Hz (in dB)
            engEnv[:,1]: Energy envelope in band 3000 < f < 10000 Hz (in dB)
    """

    ### your code here

    w = get_window(window, M)  # get the window
    fs, x = UF.wavread(inputFile)  # read in the inputFile
    mX, pX = stft.stftAnal(x, w, N, H)
    mX = 10**(mX/20.0)			# magnitude in linear scale
    numFrames = int(mX[:, 0].size)
    frmTime = H * np.arange(numFrames) / float(fs)
    binFreq = np.arange(N / 2 + 1) * float(fs) / N
    squaredMX = np.square(mX)
    lowBandIdx0 = np.where(binFreq > 0)[0][0]
    lowBandIdx3000 = np.where(binFreq < 3000)[0][-1]
    highBandIdx3000 = np.where(binFreq > 3000)[0][0]
    highBandIdx10000 = np.where(binFreq < 10000)[0][-1]
    lowBandSqMX = squaredMX[:,lowBandIdx0:lowBandIdx3000+1]
    highBandSqMX = squaredMX[:, highBandIdx3000:highBandIdx10000+1]
    engEnv = np.zeros((numFrames, 2))
    engEnv[:, 0] = 10*np.log10(np.sum(lowBandSqMX, 1))
    engEnv[:, 1] = 10*np.log10(np.sum(highBandSqMX, 1))
    return engEnv

eng = computeEngEnv('./piano.wav', 'blackman', 513, 1024, 128)
result = load(3, 1)
true_eng = result['output']
print(eng - true_eng)


#usr/bin/env/python
'''
Author  : Bhekimpilo Ndhlela
Author  : 18998712
module  : TW244 Applied Mathematics
task    : Assignment 03 problem2
since   : Wednesday-29-08-2018
'''
import matplotlib.pyplot as plt
import scipy.integrate
import numpy as np

def plot_graphs_Q2(odeint_sol, t):
    plt.title('S, I, and R against t')
    plt.xlabel('t = time')
    plt.ylabel('S, I, R')
    plt.plot(t, odeint_sol[:,0], '-r', linewidth=4, label='number Susceptible')
    plt.plot(t, odeint_sol[:,1], '-k', linewidth=4, label='number Infectious')
    plt.plot(t, odeint_sol[:,2], '-b', linewidth=4, label='number Recovered')
    plt.legend(loc='best')
    plt.grid(True, linewidth=3)
    plt.xlim([0, t[-1]])
    plt.show()

if __name__ == '__main__':
    # problem b.)
    B, G, T, x0 = .00083, .05, np.arange(0,28,1./100.), [999., 1., 0.]
    f = lambda x, t: (-B*x[1]*x[0], B*x[1]*x[0] - G*x[1], G*x[1])
    odeint_sol = scipy.integrate.odeint(f, x0, T)
    plot_graphs_Q2(odeint_sol, T)

    # problem c.)
    # i.]
    max_I = max(odeint_sol[:,1])
    # ii.]
    I_28days = odeint_sol[:,1][-1]
    print 'Maximum Number of Infected Students  : ', int(round(max_I))
    print 'Infected Students in 28 Days         : ', int(round(I_28days))

    # problem d.)
    '''
    by trial and error: Gamma = 0.6
    '''
    G, T = 0.6, np.arange(0, 56, 1./100.)
    odeint_sol = scipy.integrate.odeint(f, x0, T)
    plot_graphs_Q2(odeint_sol, T)

    # problem e.)
    #The model would be: dS/dt = -b*IS + d*I dI/dt = b*IS - (g+d)*I dR/dt = g*I

else:
    exit('USAGE: python problem2.py')


"""Negativity property."""
from typing import List, Union

import numpy as np

from toqito.state_ops import pure_to_mixed
from toqito.channels import partial_transpose

def log_negativity(rho: np.ndarray, dim: Union[List[int], int] = None) -> float:
    r"""
    Compute the log-negativity of a bipartite quantum state [WikNeg]_.

    The log-negativity of a subsystem can be defined in terms of a density matrix :math:`\rho`:

    .. math::
        E_\mathcal{N}(\rho) \equiv \text{log}_2\left( ||\rho^{\Gamma_A}||_1 \right).

    Calculate the log-negativity of the quantum state :math:`\rho`, assuming that the two subsystems
    on which :math:`\rho` acts are of equal dimension (if the local dimensions are unequal, specify
    them in the optional :code:`dim` argument).

    Examples
    ==========

    Example of the log-negativity of density matrix of Bell state.

    >>> from toqito.states import bell
    >>> from toqito.state_props import log_negativity
    >>> rho = bell(0) * bell(0).conj().T
    >>> log_negativity(rho)
    0.9999999999999997

    References
    ==========
    .. [WikNeg] Wikipedia page for negativity (quantum mechanics):
        https://en.wikipedia.org/wiki/Negativity_(quantum_mechanics)

    :param rho: A density matrix of a pure state vector.
    :param dim: The default has both subsystems of equal dimension.
    :return: A positive value that corresponds to the logarithmic negativity of
            :math:`\rho`.
    """
    # Allow the user to input either a pure state vector or a density matrix.
    rho = pure_to_mixed(rho)
    rho_dims = rho.shape
    round_dim = np.round(np.sqrt(rho_dims))

    if dim is None:
        dim = np.array([round_dim])
        dim = dim.T
    if isinstance(dim, list):
        dim = np.array(dim)

    # Allow the user to enter a single number for dim.
    if isinstance(dim, int):
        dim = np.array([dim, rho_dims[0] / dim])
        if abs(dim[1] - np.round(dim[1])) >= 2 * rho_dims[0] * np.finfo(float).eps:
            raise ValueError(
                "InvalidDim: If `dim` is a scalar, `rho` must be "
                "square and `dim` must evenly divide `len(rho)`. "
                "Please provide the `dim` array containing the "
                "dimensions of the subsystems."
            )
        dim[1] = np.round(dim[1])

    if np.prod(dim) != rho_dims[0]:
        raise ValueError(
            "InvalidDim: Please provide local dimensions in the "
            "argument `dim` that match the size of `rho`."
        )

    # Compute the log-negativity.
    return np.log2(np.linalg.norm(partial_transpose(rho, 2, dim), ord="nuc"))


# -*- coding: utf-8 -*-

import numpy as np
import scipy.io as sio

def compute_pure_rotation(K, H):
    R = np.dot(np.dot(np.linalg.pinv(K), H), K)
    for i in range(0, R.shape[1]):
        R[:, i] /= np.linalg.norm(R[:, i])
    return R

def compute_pose(K, H):
    Rt = np.dot(np.linalg.pinv(K), H)
    r1, r2 = Rt[:, 0], Rt[:, 1]
    t = Rt[:, 2]
    norm = (np.linalg.norm(r1) + np.linalg.norm(r2)) / 2
    r1 = r1 / np.linalg.norm(r1)
    r2 = r2 / np.linalg.norm(r2)
    r3 = np.cross(r1, r2)
    t /= norm
    R = np.vstack([r1, r2, r3]).T
    return R, t

def correct_rotation_matrix(R):
    U, _, V = np.linalg.svd(R)
    Rc = np.dot(U, V.T)
    return Rc

def compute_relative_rotation(K, H):
    R = compute_pure_rotation(K, H)

    print('-' * 20)
    print('Rotation matrix estimation from homography')
    print(R)

    print('Rotation matrix properties verification')

    det = np.linalg.det(R)
    print('Determinant (Should be 1): {0}'.format(det))

    inv = np.linalg.pinv(R)
    print('Matrix inverse: ')
    print(inv)

    diff = np.linalg.norm(R.T - inv) / np.linalg.norm(R.T + inv)
    print('Absolute difference between inverse matrix '
          'and its transpose: {:.9f}'.format(diff))
    print('*' * 20)

    print(diff)
    if diff > 1e-6:
        print('Matrix is not orthogonal, correcting...')
        Rc = correct_rotation_matrix(R)

        print('Corrected matrix')
        print(Rc)

        print('Determinant: {0}'.format(np.linalg.det(Rc)))
        inv = np.linalg.pinv(Rc)
        print('Matrix inverse: ')
        print(inv)

        diff = np.linalg.norm(Rc.T - inv) / np.linalg.norm(Rc.T + inv)
        print('Absolute difference between inverse matrix '
              'and its transpose: {:.9f}'.format(diff))
    print('')

if __name__ == '__main__':
    file = sio.loadmat('data/ex2.mat')
    s, ax, ay, x0, y0 = [file[x][0][0]
                         for x in ['s', 'alpha_x', 'alpha_y', 'x_0', 'y_0']]
    K = np.array([[ax, s, x0], [0, ay, y0], [0, 0, 1]])
    H1 = file['H1']
    H2 = file['H2']
    H3 = file['H3']

    compute_relative_rotation(K, H1)
    compute_relative_rotation(K, H2)
    compute_relative_rotation(K, H3)

    R, t = compute_pose(K, H3)
    print('Rotation matrix from homography H3')
    print(R)
    print('Translation vector from homography H3')
    print(t)


#!/usr/bin/env python
"""
linear regression example

"""

from __future__ import division
import os,sys
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as stats

from mylib import *

import logging
_logger = logging.getLogger("theano.gof.compilelock")
_logger.setLevel(logging.ERROR)

if not os.path.isdir("traces"):
    os.mkdir("traces")

seed = 42
n = 20
b0_true = -0.3
b1_true = 0.5
x,y = get_simple_regression_samples(n,b0=b0_true,b1=b1_true,seed=seed)

print(x)
print(y)

## plot the model
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(111)
ax.plot(x[:,0],y,'ko')
#ax.plot(x[:,0],y_pred_lstsq,color='black',label='least squares')
ax.plot(x[:,0], b0_true + x[:,0]*b1_true,color='black',label='model mean')
ax.legend()
plt.show()

## make a least squares fit
coefs_lstsq = fit_linear_lstsq(x,y)
y_pred_lstsq = coefs_lstsq[0] + (coefs_lstsq[1]*x[:,0])

## print summary
print("\n-----------------------")
print("truth: b0=%s,b1=%s"%(b0_true,b1_true))
print("lstsq fit: b0=%s,b1=%s"%(round(coefs_lstsq[0],3),round(coefs_lstsq[1],3)))


import numpy as np

def mse(y, y_hat):
    """
    Compute the mean-squared-error between y and y_hat
    :param y: array of shape (n,) : the target values
    :param y_hat: array of shape (n,) - the predicted values
    :return:
    """
    loss = np.mean((y - y_hat) ** 2)
    return loss

def mse_grad(y, y_hat, d_y_hat):
    """
    Compute the gradient of the mean-squared-error
    :param y: array of shape (n,) : the true targets
    :param y_hat: array of shape (n,) : the predicted targets
    :param d_y_hat: array of shape (n,) or a list of arrays: the gradient of the predicted targets
    :return: loss_grad : array of shape (1,) or list of arrays : the gradient of the mse loss
    """
    n = y.shape[0]
    error = (y_hat - y) / n

    if isinstance(d_y_hat, list):
        # print([d_y_hat_.shape for d_y_hat_ in d_y_hat])
        loss_grad = [np.dot(error.T, d_y_hat_) if d_y_hat_.shape[0] > 0 else np.array([]) for d_y_hat_ in d_y_hat]
        return loss_grad
    else:
        loss_grad = np.dot(error.T, d_y_hat)
        return loss_grad

def r2(y: np.ndarray, y_hat: np.ndarray) -> float:
    """
    compute the r2 score
    :param y: array of shape (n,) : the true targets
    :param y_hat: array of shape (n,) : the predicted targets
    :return: r2_value : float : the r2 score
    """
    n = y.shape[0]
    sse = n * mse(y, y_hat)
    y_mean = np.mean(y)
    sst = np.sum((y - y_mean)**2)
    r2_value = 1 - sse / sst
    return r2_value

def bic(n, mse_value, num_parameters):
    """

    :param n: int : number of data points
    :param mse_value: float : the mean-squared-error
    :param num_parameters: int : the number of free parameters of the model
    :return: bic_score : float : the bayes information criterion score:
    n * np.log(sse) + num_parameters * np.log(n)
    """
    bic_score = n * np.log(n * mse_value) + np.log(n) * num_parameters
    return bic_score


import numpy as np

def least_squares(X, y):
    """
    Perform least squares regression.
    Finds coefficients w such that Xw ~= y

    Parameters
    ----------
    X : np.array, shape (n_samples, n_features)
        The input data
    y : np.array, shape (n_samples)
        The input targets

    Returns
    -------
    w : np.array, shape (n_features)
        The predicted regression coefficients
    """
    n, p = X.shape
    response = np.dot(X.T, y)  # Build response
    return np.linalg.solve(np.dot(X.T, X), response)

if __name__ == '__main__':
    n, p = 10, 3
    X = np.random.randn(n, p)
    true_coefs = np.random.randn(p)
    y = np.dot(X, true_coefs) + 0.01 * np.random.randn(n)
    pred_coefs = least_squares(X, y)
    print(pred_coefs)
    print(true_coefs)


import numpy as np

def cross_entropy(T, Y):
    # -sum(t_i*ln(y_i) + (1-t_i)ln(1-y_i)) = -sum(t_i*ln(y_i)) - sum((1-t_i)ln(1-y_i))
    return -np.dot(T, np.log(Y)) - np.dot(1-T, np.log(1-Y))

def sigmoid(a):
    return 1 / (1 + np.exp(-a))


import numpy as np

def getPerspectiveTransform(view1_4_pts, view2_4_pts):
    '''
    source https://math.stackexchange.com/questions/494238/how-to-compute-homography-matrix-h-from-corresponding-points-2d-2d-planar-homog
    '''
    # loop through the 4 correspondences and create assemble matrix
    a_list = []
    for i in range(4):
        p1 = np.matrix([view1_4_pts[i][0,0], view1_4_pts[i][0,1], 1])
        p2 = np.matrix([view2_4_pts[i][0,0], view2_4_pts[i][0,1], 1])

        a2 = [0, 0, 0, -p2.item(2) * p1.item(0), -p2.item(2) * p1.item(1), -p2.item(2) * p1.item(2),
              p2.item(1) * p1.item(0), p2.item(1) * p1.item(1), p2.item(1) * p1.item(2)]
        a1 = [-p2.item(2) * p1.item(0), -p2.item(2) * p1.item(1), -p2.item(2) * p1.item(2), 0, 0, 0,
              p2.item(0) * p1.item(0), p2.item(0) * p1.item(1), p2.item(0) * p1.item(2)]

        a_list.append(a1)
        a_list.append(a2)

    matrix_A = np.matrix(a_list)

    # svd decomposition
    u, s, v = np.linalg.svd(matrix_A)

    # reshape the min singular value into 3x3 matrix
    h = np.reshape(v[8], (3, 3))

    # normalize h
    h = (1/h.item(8)) * h
    return h


import numpy as np
import matplotlib.pyplot as plt
import random as r

def getPiFromPolygonalApproximation(N, R):
    angles = np.linspace(0, 2 * np.pi, N+1)
    points = [(R*np.cos(angle), R*np.sin(angle))
              for angle in angles]
    distances = []
    for i in range(len(points)-1):
        p1 = points[i]
        p2 = points[i + 1]
        distance = np.hypot(p2[0] - p1[0], p2[1] - p1[1])
        distances.append(distance)
    return sum(distances) / (2 * R)

def getPiFromMonteCarlo(N):
    inside = 0
    for i in range(0, N):
        x = r.random()
        y = r.random()
        if (np.hypot(x, y) < 1.0):
            inside += 1
    return float(inside/N)*4

def getPiFromGregoryLeibniz(N):
    pi = 1
    for i in range(1, N):
        pi += ((-1)**i)*1/(i*2 + 1)
    return 4*pi

def getPiFromNilakantha(N):
    pi = 3
    for i in range(1, N):
        pi += ((-1)**(i-1))*4/((2*i*(2*i+1)*(2*i+2)))
    return pi

def plotFigure(points):
    x = [point[0] for point in points]
    y = [point[1] for point in points]
    plt.plot(x, y, ".")
    plt.title("Approximation of a circle using polygon with N = {}".format(
        len(points) - 1), fontsize=15)
    plt.axis("equal")
    plt.xlabel("X")
    plt.ylabel("Y")
    plt.show()
    plt.close()

def plotFigureMonteCarlo(inside, outside):
    x1 = [point[0] for point in inside]
    y1 = [point[1] for point in inside]
    x2 = [point[0] for point in outside]
    y2 = [point[1] for point in outside]
    plt.plot(x1, y1, "r.")
    plt.plot(x2, y2, "b.")
    plt.title("Approximation of $\pi$ using Monte Carlo method", fontsize=15)
    plt.xlabel("X")
    plt.ylabel("Y")
    plt.show()
    plt.close()

piFromPolygonalApproximation = getPiFromPolygonalApproximation(1000, 1)
piFromMonteCarlo = getPiFromMonteCarlo(1000)
piFromGregoryLeibniz = getPiFromGregoryLeibniz(1000)
piFromNilakantha = getPiFromNilakantha(1000)

print("Polygonal Appproximation:")
print("π = {}".format(piFromPolygonalApproximation))

print("Monte Carlo Appproximation:")
print("π = {}".format(piFromMonteCarlo))

print("Gregory Leibniz Serie:")
print("π = {}".format(piFromGregoryLeibniz))

print("Nilakantha Serie:")
print("π = {}".format(piFromNilakantha))


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import norm
from sympy.solvers import solve
from sympy import *
import math
w1_mu = 5
w1_varian = 2
w1_sigma = math.sqrt(w1_varian)
w1_prior = 0.5
w1_x = np.linspace(w1_mu - 3*w1_sigma, w1_mu + 3*w1_sigma, 100)
plt.plot(w1_x, w1_prior*norm.pdf(w1_x, w1_mu, w1_sigma), label="w1 class (Happy cat)")
w2_mu = 0
w2_varian = 2
w2_sigma = math.sqrt(w2_varian)
w2_prior = 0.5
w2_x = np.linspace(w2_mu - 3*w2_sigma, w2_mu + 3*w2_sigma, 100)
plt.plot(w2_x, w2_prior*norm.pdf(w2_x, w2_mu, w2_sigma), label="w2 class (Sad cat)")
x = Symbol('x')
dec_boundry = solve(w1_prior*(1/(w1_sigma*sqrt(2*pi)))*(exp((-1/2)*((x-w1_mu)/w1_sigma)**2))-w2_prior*(1/(w2_sigma*sqrt(2*pi)))*(exp((-1/2)*((x-w2_mu)/w2_sigma)**2)), x, rational=False)
print("decision boundry for equaled priors = ", dec_boundry[0])
w1_prior = 0.8
w2_prior = 0.2
dec_boundry = solve(w1_prior*(1/(w1_sigma*sqrt(2*pi)))*(exp((-1/2)*((x-w1_mu)/w1_sigma)**2))-w2_prior*(1/(w2_sigma*sqrt(2*pi)))*(exp((-1/2)*((x-w2_mu)/w2_sigma)**2)), x, rational=False)
print("decision boundry for prior=0.8 for happy cat = ", dec_boundry[0])
plt.savefig("T2")
plt.show()
print("changing the distribution of the second class...")
w2_mu = 0
w2_varian = 4
w2_sigma = math.sqrt(w2_varian)
w1_prior = 0.5
w2_prior = 0.5
dec_boundry = solve(w1_prior*(1/(w1_sigma*sqrt(2*pi)))*(exp((-1/2)*((x-w1_mu)/w1_sigma)**2))-w2_prior*(1/(w2_sigma*sqrt(2*pi)))*(exp((-1/2)*((x-w2_mu)/w2_sigma)**2)), x, rational=False)
print("decision boundry for the new distribution of Sad cat = ", dec_boundry[0])
w1_x = np.linspace(w1_mu - 3*w1_sigma, w1_mu + 3*w1_sigma, 100)
plt.plot(w1_x, w1_prior*norm.pdf(w1_x, w1_mu, w1_sigma), label="w1 class (Happy cat)")
w2_x = np.linspace(w2_mu - 3*w2_sigma, w2_mu + 3*w2_sigma, 100)
plt.plot(w2_x, w2_prior*norm.pdf(w2_x, w2_mu, w2_sigma), label="w2 class (Sad cat)")
plt.savefig("OT1")
plt.show()


import numpy as np

# 7E1
'''
1) continuous (smoothness)
2) additive
3) increasing in #possible events
'''

# 7E2
e = -0.7*np.log2(0.7) - 0.3 * np.log2(0.3)
print('%.4f' % e)

# 7E3
p = [0.2, 0.25, 0.25, 0.3]
e = 0.

for pp in p:
	e -= pp * np.log2(pp)

print('%.4f' % e)

# 7E4
e = -np.log2(1. / 3.)
print('%.4f' % e)


import numpy as np

def cart_to_pol(nparr: np.ndarray) -> np.ndarray:
    r = np.sqrt(nparr[0] ** 2 + nparr[1] ** 2)
    theta = np.arctan2(nparr[1], nparr[0])
    return np.asarray([r, theta])

def pol_to_cart(nparr: np.ndarray) -> np.ndarray:
    x = nparr[0] * np.cos(nparr[1])
    y = nparr[0] * np.sin(nparr[1])
    return np.asarray([x, y])


import numpy as np
from .sigmoid import sigm

def getCost(X, y, theta):
    # Calculating matrix multiplication of X and theta
    X_x_theta = np.matmul(X, theta)
    # Getting the sigmoid values for calculating hypothesis function
    h_theta = sigm(X_x_theta)
    # Caluclating transpose of X which is used for calculating gradient
    X_trans = np.transpose(X)

    # Performing logarthmic operation on each element of matrix
    log_h_theta = np.log(h_theta)
    log_1_h_theta = np.log(1-h_theta)

    log_h_tht_trans = np.transpose(log_h_theta)
    log_1_h_tht_trans = np.transpose(log_1_h_theta)

    m = len(y)
    # Calculating Cost
    cost_J = (-1/m) * (np.matmul(log_h_tht_trans, y) + np.matmul(log_1_h_tht_trans, (1 - y)))

    # Calculating Gradient.
    grad = (1/m) * (np.matmul(X_trans, (h_theta - y)))

    return cost_J, grad