#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import numpy as np

dat = [
    [300, 0.024],
    [400, 0.035],
    [500, 0.046],
    [600, 0.058],
    [700, 0.067],
    [800, 0.083],
    [900, 0.097],
    [1000, 0.111],
    [1100, 0.125],
]

class CubicSpline(object):

    """
    """

    def __init__(self, dat):
        self.dat = dat
        self._make_h()
        self._make_x()
        self._make_a()
        self._make_c()
        self._make_d()
        self._make_b()

    def _make_h(self):
        self.h = []
        for i in range(0, len(self.dat) - 1):
            #: x[i+1] - x[i]
            self.h.append(self.dat[i + 1][0] - self.dat[i][0])

    def _make_a(self):
        #: ai = yi
        self.a = [_[1] for _ in self.dat]

    def _make_x(self):
        #: ai = yi
        self.x = [_[0] for _ in self.dat]

    def _make_c(self):
        A, C = self._make_tridiagonal()
        self.c = list(np.linalg.solve(A, C))

    def _make_d(self):
        self.d = []

        for i in range(0, len(self.dat) - 3):
            _d = (self.c[i + 1] - self.c[i]) / 3 * self.h[i]
            self.d.append(_d)

    def _make_b(self):
        self.b = []

        for i in range(0, len(self.dat) - 3):
            _b = ((self.a[i + 1] - self.a[i]) / self.h[i]) - ((2 * self.c[i] + self.c[i + 1]) * self.h[i] / 3)
            self.b.append(_b)

    def _make_tridiagonal(self):
        d = []
        lim = len(self.dat) - 2
        A = []
        B = []
        C = []

        for i in range(lim):
            row = []
            if i == 0:
                s = self.Eq9()
                row.append(s[0])
                row.append(s[1])
                C.append(s[2])
                row += [0] * (lim - 2)
            elif i == (lim - 1):
                s = self.Eq10()
                row += [0] * (lim - 2)
                row.append(s[0])
                row.append(s[1])
                C.append(s[2])
            else:
                s = self.Astar(i)
                row += [0] * (i - 1)
                row.append(s[0])
                row.append(s[1])
                row.append(s[2])
                C.append(s[3])
                row += [0] * (lim - (i - 1) - 3)
            A.append(row)
        return A, C

    def Astar(self, i):
        a = self.h[i - 1]
        b = 2 * (self.h[i - 1] + self.h[i])
        c = self.h[i]
        d = 3 * ((self.a[i + 1] - self.a[i]) / self.h[i]) - 3 * ((self.a[i] - self.a[i - 1]) / self.h[i - 1])
        return a, b, c, d

    def Eq9(self):
        a = (3 * self.h[0]) + (2 * self.h[1]) + (self.h[0] ** 2 / self.h[1])
        b = self.h[1] - (self.h[0] ** 2 / self.h[1])
        c = (3 * (self.a[2] - self.a[1]) / self.h[1]) - (3 * (self.a[1] - self.a[0]) / self.h[0])
        return a, b, c

    def Eq10(self):
        n = len(self.dat) - 1
        a = self.h[n - 2] - (self.h[n - 1] ** 2 / self.h[n - 2])
        b = 3 * self.h[n - 1] + 2 * self.h[n - 1] + (self.h[n - 1] ** 2 / self.h[n - 2])
        c = (3 * (self.a[n] - self.a[n - 1]) / self.h[n - 1]) - (3 * (self.a[n - 1] - self.a[n - 2]) / self.h[n - 2])
        return a, b, c

    def print_eq(self):
        eq = "<{0}> + <{1}>(x - <{2}>) + <{3}>(x - <{4}>)^2 + <{5}>(x - <{6}>)^3"
        e = []
        for i in range(len(self.dat) - 3):
            e.append(eq.format(self.a[i], self.b[i], self.x[i], self.c[i], self.x[i], self.d[i], self.x[i]))
        return e

if __name__ == "__main__":
    sol = CubicSpline(dat)
    A,C = sol._make_tridiagonal()
    print(np.array(A))
    print(np.array(C))
    print(np.array(sol.print_eq()))


import numpy as np
import pydrake.symbolic as sym

def car_continuous_dynamics(x, u):
    # x = [x position, y position, heading, speed, steering angle]
    # u = [acceleration, steering velocity]
    m = sym if x.dtype == object else np  # Check type for autodiff
    heading = x[2]
    v = x[3]
    steer = x[4]
    x_d = np.array([
        v*m.cos(heading),
        v*m.sin(heading),
        v*m.tan(steer),
        u[0],
        u[1]
    ])
    return x_d

def discrete_dynamics(x, u, dt):
    # forward Euler
    x_next = x + dt * car_continuous_dynamics(x, u)
    return x_next

def rollout(x0, u_trj, n_x, dt):
    ''' Returns state trajectory projected in time, x_trj: [N, number of states] '''
    x_trj = np.zeros((n_x, u_trj.shape[1]+1))
    x_trj[:, 0] = x0
    for n in range(1, u_trj.shape[1]+1):
        x_trj[:, n] = discrete_dynamics(x_trj[:, n-1], u_trj[:, n-1], dt)
    return x_trj

class derivatives():
    def __init__(self, discrete_dynamics, n_x, n_u):
        self.x_sym = np.array([sym.Variable("x_{}".format(i))
                              for i in range(n_x)])
        self.u_sym = np.array([sym.Variable("u_{}".format(i))
                              for i in range(n_u)])
        x = self.x_sym
        u = self.u_sym
        f = car_continuous_dynamics(x, u)
        self.f_x = sym.Jacobian(f, x)
        self.f_u = sym.Jacobian(f, u)

    def get_jacobians(self, x, u):
        env = {self.x_sym[i]: x[i] for i in range(x.shape[0])}
        env.update({self.u_sym[i]: u[i] for i in range(u.shape[0])})
        f_x = sym.Evaluate(self.f_x, env)
        f_u = sym.Evaluate(self.f_u, env)
        return f_x, f_u

def get_linear_dynamics(derivs, x, u):
    ''' Linearize dynamics to take form dx = x[n+1] - x[n] = A*dx + B*du '''
    f_x, f_u = derivs.get_jacobians(x, u)
    A = f_x
    B = f_u
    return A, B

def step_car_dynamics(x0, u0, sigmas, dt):
    ''' For simulation '''
    n_x = x0.shape[0]
    xn = discrete_dynamics(x0, u0, dt)
    w = np.random.normal(0, sigmas, n_x)
    return xn + w

if __name__ == "__main__":
    dt = 0.1
    n_x = 5
    n_u = 2
    derivs = derivatives(discrete_dynamics, n_x, n_u)
    dfx, dfu = derivs.f_x, derivs.f_u
    print(dfx, dfu)


import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from scipy import misc
import scipy

from matplotlib.pyplot import imread

def Generte_Masks(sigma,T):
    '''
    A method for generating Gaussian Derivative Filters.
    Arguments

    sigma: Standard Deviation of Gaussian
    T:     (T>=0.5) Threshold the gaussian for selecting
            a finite segment of gaussian

    '''
    assert sigma>=0.5
    #Calculating Size of Filter By threshholding the value of Gaussian at T
    half_size=np.round(np.sqrt(-np.log(T)*2*sigma**2))
    size=half_size*2+1

    normal = 1 / (2.0 * np.pi * sigma**2)
    # Creating X and Y axis
    x, y = np.mgrid[-half_size:half_size+1, -half_size:half_size+1]

    # Creating Filters
    g =  np.exp(-((x**2 + y**2) / (2.0*sigma**2))) * normal
    Gx =  (-x/(sigma**2))*np.exp(-((x**2 + y**2) / (2.0*sigma**2))) * normal
    Gy =  (-y/(sigma**2))*np.exp(-((x**2 + y**2) / (2.0*sigma**2))) * normal
    # Scaling by 255
    Gx=np.round(Gx*255)
    Gy=np.round(Gy*255)

    return Gx,Gy

def rgb2gray(rgb):
    '''
    A method for converting RGB to Gray scale
    '''

    return np.dot(rgb[...,:3], [0.2989, 0.5870, 0.1140])

def hysteresis(img, TI, TH):
    '''
    An Implementation of hysteresis By getting iterating over all pixels
    which have values equal or greater than TI and its aat leat one from
    eight neighbours is greater than TH.
    It is recursive because I am updating same Image.
    ARGUMENTS:
    img: input Image
    TI:  lower threshold
    TH:  upper threshold

    '''
    M, N = img.shape
    for i in range(1, M-1):
        for j in range(1, N-1):
            if ((img[i,j] >= TI) and (img[i,j] < TH)):
                try:
                    if ((img[i+1, j-1] >= TH) or (img[i+1, j] >= TH) or (img[i+1, j+1] >= TH)
                        or (img[i, j-1] >= TH) or (img[i, j+1] >= TH)
                        or (img[i-1, j-1] >= TH) or (img[i-1, j] >= TH) or (img[i-1, j+1] >= TH)):

                        img[i, j] = max( img[i+1, j-1]  , img[i+1, j]   , img[i+1, j+1], img[i, j-1] ,
                                         img[i, j+1]    , img[i-1, j-1] ,img[i-1, j]  ,img[i-1, j+1] )
                    else:
                        img[i, j] = 0
                except IndexError as e:
                    pass
    return img


import numpy as np
import ast

def lagrangeInterpolation(x,y):
    x = ast.literal_eval(x)
    y = ast.literal_eval(y)
    y = np.array(y)
    x = np.array(x)
    nx = len(x)
    res = {}
    Li = []

    # fill the y[i]*L[i]x
    for xi in range(nx):
        Li.append({"numerator": "", "multiplier": 0, "multiplier2": 0})
        auxMultiplier = 1
        for xi2 in range(nx):
            if (xi != xi2):
                auxNumerator = round(-1 * x[xi2],4)
                if (auxNumerator >= 0):
                    auxNumerator = "+" + str(auxNumerator)
                else:
                    auxNumerator = str(auxNumerator)
                Li[xi]['numerator'] += "*(x" + auxNumerator + ")"

                auxMultiplier *= x[xi] - x[xi2]

        Li[xi]['multiplier'] = round(1 / auxMultiplier,4)
        Li[xi]['multiplier2'] = round(y[xi] * (1 / auxMultiplier),4)

    Li.append({"result": ""})

    L = []
    for i in range(0, len(Li) - 1):
        if (Li[i]["multiplier"] >= 0):
            Li[len(Li) - 1]["result"] += "+"

        Li[len(Li) - 1]["result"] += str(Li[i]['multiplier2']) + Li[i]['numerator']
        string = str(round(Li[i]['multiplier'],4))+str((Li[i]['numerator']))
        string = string.replace(" ", "").replace("--", "+").replace("++", "+").replace("+-", "-").replace("-+", "-")
        L.append({"index":i, "L" : string })
    result = Li[len(Li) - 1]["result"]
    result = result.replace(" ", "").replace("--", "+").replace("++", "+").replace("+-", "-").replace("-+", "-")
    res["L"] = L
    res["result"] = result
    return res


import numpy as np
import timeit

NUM_RUNS = 10

def mat_trig_upper_dot(mat, v, y, n):

    for i in range(n-1,-1,-1):
        for j in range(n-1, i-1, -1):
            y[i] += mat[i][j] * v[j]

    return y

def mat_trig_upper_axpy(mat, v, y, n):
    for j in range(n-1,-1,-1):
        for i in range(j+1):
            y[i] += mat[i][j] * v[j]

    return y

def mat_trig_lower_dot(mat, v, y, n):
    for i in range(n):
        for j in range(i+1):
            y[i] += mat[i][j] * v[j]

    return y

def mat_trig_lower_axpy(mat, v, y, n):
    for j in range(n):
        for i in range(n-1, j-1, -1):
            y[i] += mat[i][j] * v[j]

    return y

def mat_sym_trig_upper_dot(mat, v, y, n):
    for i in range(n):
        for j in range(n):
            if i > j:
                y[i] += mat[j][i] * v[j]
            else:
                y[i] += mat[i][j] * v[j]

    return y

def mat_vector_dot(mat, v, y, n):
    for i in range(n):
        for j in range(n):
            y[i] += mat[i][j] * v[j]
    return y

def assertion(func, mat, v, n):

    y = np.zeros(n, dtype=int)

    # numpy
    dot = np.dot(mat, v)

    # mine
    my_dot = func(mat, v, y, n)

    return np.array_equal(dot, my_dot)

def make_tests():
    mat_trig_upper = np.array([
        [1,2,3],
        [0,4,5],
        [0,0,6]
    ])

    mat_trig_lower = np.array([
        [1,0,0],
        [2,3,0],
        [4,5,6]
    ])

    mat_sym = np.array([
        [1,2,3],
        [2,5,6],
        [3,6,7]
    ])

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

    v = np.array([1,0,1])
    n = len(v)

    # Testes
    assert assertion(mat_trig_upper_dot,mat_trig_upper,v,n)
    print("Matriz Triangular Superior Dot - [!] PASSOU")

    assert assertion(mat_trig_lower_dot,mat_trig_lower,v,n)
    print("Matriz Triangular Inferior Dot - [!] PASSOU")

    assert assertion(mat_sym_trig_upper_dot,mat_sym,v,n)
    print("Matriz Simétrica Dot - [!] PASSOU")

    assert assertion(mat_vector_dot,mat,v,n)
    print("Matriz-Vetor Dot - [!] PASSOU\n\n")

def benchmark(func, msg=None, *args):
    duration = timeit.Timer(lambda: func(*args)).timeit(number = NUM_RUNS)

    avg_duration = duration/NUM_RUNS

    if msg:
        print(f'[+] {msg}: Demorou, aproximadamente, {avg_duration} segundos')

if "__main__" == __name__:

    make_tests()

    n = 3000
    v = np.random.randint(low=1, high=5, size=n, dtype=int)
    mat = np.random.randint(low=1, high=10, size=(n, n), dtype=int)

    mat_trig_upper = np.triu(mat)
    mat_trig_lower = np.tril(mat)
    #mat_sym = np.tril(mat) + np.tril(mat, -1).T

    print("### Dot: Matriz Triangular Superior ###")

    benchmark(np.dot, "Numpy func", mat_trig_upper, v)

    y = np.zeros(n, dtype=int)
    benchmark(mat_vector_dot, "Naive func", mat_trig_upper, v, y, n)

    y = np.zeros(n, dtype=int)
    benchmark(mat_trig_upper_dot, "Efficient func", mat_trig_upper, v, y, n)

    print("### Dot: Matriz Triangular Inferior ###")

    benchmark(np.dot, "Numpy func", mat_trig_lower, v)

    y = np.zeros(n, dtype=int)
    benchmark(mat_vector_dot, "Naive func", mat_trig_lower, v, y, n)

    y = np.zeros(n, dtype=int)
    benchmark(mat_trig_lower_dot, "Efficient func", mat_trig_lower, v, y, n)

    print("### AxPy: Matriz Triangular Superior ###")

    benchmark(np.dot, "Numpy func", mat_trig_upper, v)

    y = np.zeros(n, dtype=int)
    benchmark(mat_vector_dot, "Naive func", mat_trig_upper, v, y, n)

    y = np.zeros(n, dtype=int)
    benchmark(mat_trig_upper_axpy, "Efficient func", mat_trig_upper, v, y, n)

    print("### AxPy: Matriz Triangular Inferior ###")

    benchmark(np.dot, "Numpy func", mat_trig_lower, v)

    y = np.zeros(n, dtype=int)
    benchmark(mat_vector_dot, "Naive func", mat_trig_lower, v, y, n)

    y = np.zeros(n, dtype=int)
    benchmark(mat_trig_lower_axpy, "Efficient func", mat_trig_lower, v, y, n)


import numpy as np
import tensorflow as tf

@tf.function
def unit_interval(x, xmin, xmax, scale_factor=1.):
    """
    Rescale tensor values to lie on the unit interval.

    If values go beyond the stated xmin/xmax, they are rescaled
    in the same way, but will be outside the unit interval.

    Parameters
    ----------
    x : Tensor
        Input tensor, of any shape.

    xmin, xmax : float
        Minimum and maximum values, which will be rescaled to the
        boundaries of the unit interval: [xmin, xmax] -> [0, 1].

    scale_factor : float, optional
        Scale the unit interval by some arbitrary factor, so that
        the output tensor values lie in the interval [0, scale_factor].

    Returns
    -------
    y : Tensor
        Rescaled version of x.
    """
    return scale_factor * (x - xmin) / (xmax - xmin)

def build_hex_array(hex_spec=(3,4), ants_per_row=None, d=14.6):
    """
    Build an antenna position dict for a hexagonally close-packed array.

    Parameters
    ----------
    hex_spec : tuple, optional
        If `ants_per_row = None`, this is used to specify a hex array as
        `hex_spec = (nmin, nmax)`, where `nmin` is the number of antennas in
        the bottom and top rows, and `nmax` is the number in the middle row.
        The number per row increases by 1 until the middle row is reached.

        Default: (3,4) [a hex with 3,4,3 antennas per row]

    ants_per_row : array_like, optional
        Number of antennas per row. Default: None.

    d : float, optional
        Minimum baseline length between antennas in the hex array, in meters.
        Default: 14.6.

    Returns
    -------
    ants : dict
        Dictionary with antenna IDs as the keys, and tuples with antenna
        (x, y, z) position values (with respect to the array center) as the
        values. Units: meters.
    """
    ants = {}

    # If ants_per_row isn't given, build it from hex_spec
    if ants_per_row is None:
        r = np.arange(hex_spec[0], hex_spec[1]+1).tolist()
        ants_per_row = r[:-1] + r[::-1]

    # Assign antennas
    k = -1
    y = 0.
    dy = d * np.sqrt(3) / 2. # delta y = d sin(60 deg)
    for j, r in enumerate(ants_per_row):

        # Calculate y coord and x offset
        y = -0.5 * dy * (len(ants_per_row)-1) + dy * j
        x = np.linspace(-d*(r-1)/2., d*(r-1)/2., r)
        for i in range(r):
            k += 1
            ants[k] = (x[i], y, 0.)

    return ants


from sympy import * # Module to work with symbolic variables
from sympy.abc import _clash1 # So that sympify will be able to convert string to symbolic objects
from findExtremums import findExtremums

# Checks a numbers parity
def isOdd(number: int):
    return False if number % 2 == 0 else True

# Receives a function as a string and evaluate it at a chosen value
def funEvaluate(function, variable, value: float):
    try: # Try and convert the function from string to symbolic
        function = sympify(function)
    except: # If it fails, print error message
        raise Exception("The entered function string was formatted wrongly. Try something like: '3*x*exp(-7*x**2)'.")

    try: # Same goes for for the variable
        variable = sympify(variable)
    except:
        raise Exception("The entered variable is invalid.")

    # Actually evaluate the function and return the result
    return (function.subs(variable,value)).evalf()

# Return max and min values for a function in specified interval
def intervalMaxMin(func, infLim, supLim, var = 'x'):
    var = sympify(var)
    func = sympify(func)
    maxList, minList, inflection = findExtremums(func, var)

    # [infLim,supLim] --> a = func(infLim) , b = func(supLim)
    a = funEvaluate(func,var,infLim)
    b = funEvaluate(func,var,supLim)

    maxValue = max(a,b)
    minValue = min(a,b)

    for point in maxList:
        if point < supLim and point > infLim:
            aux = funEvaluate(func,var,point)
            if aux > maxValue: maxValue = aux

    for point in minList:
        if point < supLim and point > infLim:
            aux = funEvaluate(func,var,point)
            if aux < minValue: minValue = aux

    for point in inflection:
        if point < supLim and point > infLim:
            aux = funEvaluate(func,var,point)
            if aux > maxValue: maxValue = aux
            elif aux < minValue: minValue = aux

    return maxValue, minValue

# Substitutes a matrix's row or column for another
def subMatrix(matrix: Matrix, sub: str, index: int, new: Matrix):
    if sub == "row":
        matrix.row_del(index)
        matrix = matrix.row_insert(index, new)

    elif sub == "col":
        matrix.col_del(index)
        matrix = matrix.col_insert(index, new)

    else:
        raise Exception("The argument 'sub' accepts only 'row' or 'col'.")

    return matrix

# Swap a matrix's rows or columns
def swapMatrix(matrix: Matrix, sub: str, i: int, j: int):
    if sub == "row":
        tmp = matrix.row(i)
        matrix = subMatrix(matrix, sub, i, matrix.row(j))
        matrix = subMatrix(matrix, sub, j, tmp)

    elif sub == "col":
        tmp = matrix.col(i)
        matrix = subMatrix(matrix, sub, i, matrix.col(j))
        matrix = subMatrix(matrix, sub, j, tmp)

    else:
        raise Exception("The argument 'sub' accepts only 'row' or 'col'.")

    return matrix

if __name__ == "__main__":
    print(intervalMaxMin("E**x**2-4*x",0,1))
    print(intervalMaxMin("E**x**2-4*x",1,2))


import sys, math
import re
import math
from scipy.special import erfinv
import time
import os

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.gridspec as gridspec
from copy import deepcopy
from scipy import linalg
from scipy.optimize import curve_fit

import warnings
warnings.filterwarnings("ignore");

SNUMBER = pow(10, -124);

#https://stephens999.github.io/fiveMinuteStats/inverse_transform_sampling.html
def expInvCDF(L, x):
    return -np.log(1 - x)/L;
def gausInvCDF(mu, sig, x):
    return erfinv(2*x - 1)*sig*np.sqrt(2) + mu;

def main():
    verbosity = 1;
    binN = 100;
    rangeX = [0.0, 3.0];

    np.random.seed(2);
    sampleN = 2000;
    expL = 2.0;
    gausMu = 1.5;
    gausSig = 0.5;
#histogram
    nbins = np.linspace(rangeX[0], rangeX[1], binN);
    uniformPDF = np.random.uniform(0, 1, sampleN);
    uniformHist = np.zeros(binN);
    expHist = np.zeros(binN);
    gausHist = np.zeros(binN);
    for x in uniformPDF:
        if rangeX[0] < x and x < rangeX[1]:
            uniformHist[int(binN*(x-rangeX[0])/(rangeX[1]-rangeX[0]))] += 1;
        expX = expInvCDF(expL, x);
        if rangeX[0] < expX and expX < rangeX[1]:
            expHist[int(binN*(expX-rangeX[0])/(rangeX[1]-rangeX[0]))] += 1;
        gausX = gausInvCDF(gausMu, gausSig, x);
        if rangeX[0] < gausX and gausX < rangeX[1]:
            gausHist[int(binN*(gausX-rangeX[0])/(rangeX[1]-rangeX[0]))] += 1;
#plots
    fig = plt.figure(figsize=(18, 14));
    gs = gridspec.GridSpec(2, 2);
    ax0 = fig.add_subplot(gs[0]);
    ax1 = fig.add_subplot(gs[1]);
    ax2 = fig.add_subplot(gs[2]);
    ax3 = fig.add_subplot(gs[3]);
    #plot 0
    ax0.plot(nbins, uniformHist, linewidth=2,color="blue",linestyle="steps-mid");
    ax0.set_title("Uniform Distribution", fontsize=24, y=1.03);
    ax0.set_xlabel("x", fontsize=18);
    ax0.set_ylabel("count", fontsize=18);
    ax0.set_xlim(rangeX[0], rangeX[1]);
    ax0.set_ylim(bottom=0);
    #plot 1
    ax1.plot(nbins, expHist, linewidth=2,color="blue",linestyle="steps-mid");
    ax1.set_title("Exopential Distribution", fontsize=24, y=1.03);
    ax1.set_xlabel("x", fontsize=18);
    ax1.set_ylabel("count", fontsize=18);
    ax1.set_xlim(rangeX[0], rangeX[1]);
    ax1.set_ylim(bottom=0);
    #plot 2
    ax2.plot(nbins, gausHist, linewidth=2,color="blue",linestyle="steps-mid");
    ax2.set_title("Normal Distribution", fontsize=24, y=1.03);
    ax2.set_xlabel("x", fontsize=18);
    ax2.set_ylabel("count", fontsize=18);
    ax2.set_xlim(rangeX[0], rangeX[1]);
    ax2.set_ylim(bottom=0);
#save plots
    exepath = os.path.dirname(os.path.abspath(__file__));
    filenameFig = exepath + "/fig/invTransSampling.png";
    gs.tight_layout(fig);
    plt.savefig(filenameFig);

    if verbosity >= 1:
        print("Creating the following files:");
        print(filenameFig);

if __name__ == "__main__":
    print("\n##############################################################Head");
    main();
    print("##############################################################Tail");


import numpy as np
from random import shuffle

def softmax_loss_naive(W, X, y, reg):
    """
    Softmax loss function, naive implementation (with loops)

    Inputs have dimension D, there are C classes, and we operate on minibatches
    of N examples.

    Inputs:
    - W: A numpy array of shape (D, C) containing weights.
    - X: A numpy array of shape (N, D) containing a minibatch of data.
    - y: A numpy array of shape (N,) containing training labels; y[i] = c means
      that X[i] has label c, where 0 <= c < C.
    - reg: (float) regularization strength

    Returns a tuple of:
    - loss as single float
    - gradient with respect to weights W; an array of same shape as W
    """
    num_train = X.shape[0]
    num_classes = W.shape[1]

    # Initialize the loss and gradient to zero.
    loss = 0.0
    dW = np.zeros_like(W)

    # Calculate scores for each class
    scores = np.dot(X, W)
    scores -= np.max(scores, axis=1)[:, np.newaxis]

    # Iterate for each image in input tensor X
    for i in np.arange(0, num_train):
        exp_sum = np.sum(np.exp(scores[i]))
        loss += -scores[i][y[i]] + np.log(exp_sum)

        # Calculate the contribution of each class to overall gradient
        for j in np.arange(0, num_classes):
            dW[:, j] += X[i] * ((np.exp(scores[i][j]) / exp_sum) - (j == y[i]))

    # Average and add a regularisation term
    loss = loss / num_train + 0.5 * reg * np.sum(W * W)
    dW = dW / num_train + reg * W

    return loss, dW

def softmax_loss_vectorized(W, X, y, reg):
    """
    Softmax loss function, vectorized version.

    Inputs and outputs are the same as softmax_loss_naive.
    """
    num_train = X.shape[0]
    num_classes = W.shape[1]

    # Initialize the loss and gradient to zero.
    loss = np.float64(0)
    dW = np.zeros_like(W, dtype=np.float64)
    scores = np.zeros((num_train, num_classes), dtype=np.float64)

    scores += np.dot(X, W)
    scores -= np.max(scores, axis=1)[:, np.newaxis]

    exp_scores = np.exp(scores)
    loss = np.sum(-scores[np.arange(0, num_train), y] + np.log(np.sum(exp_scores, axis=1)))
    loss = loss / num_train + 0.5 * reg * np.sum(np.square(W))

    exp_scores /= np.sum(exp_scores, axis=1)[:, np.newaxis]
    exp_scores[np.arange(0, num_train), y] -= 1

    dW = np.dot(X.T, exp_scores)
    dW = dW / num_train + reg * W

    return loss, dW


import numpy as np

__all__ = ['pooledVar', 'moving_average']

def moving_average(x,w,mode='linear'):
    """
    Moving Average for an Array.

    Parameters
    ----------
    x: Array Vector
        The arrays must have the same shape, except in the dimension.

    w: Int
        Window size

    mode: {linear, exp}
        Linear: Simple Moving Average
        Exponential: Exponetial Weighted Moving Average

    Returns
    -------
    mean : array
        The calculated moving average.
    """
    if mode == 'linear':
        weight = np.ones(w)/w
    elif mode == 'exp':
        weight = np.exp(-np.linspace(0,w,w))
    x = np.array(x)
    mean = np.convolve(x,weight, mode='full')[:-w+1]

    return mean

def pooledVar(x, y):
    """
    Pooled Variance of two samples.

    Parameters
    ----------
    x, y : 1 dimensional array_like
        The arrays must have the same shape, except in the dimension.

    Returns
    -------
    sp : float
        The calculated pooled variance.
    """
    n1 = np.size(x)
    n2 = np.size(y)
    s1 = np.var(x, ddof=1)
    s2 = np.var(y, ddof=1)
    return ((n1-1)*s1+(n2-1)*s2)/(n1+n2-2)


from __future__ import print_function
import sys
from galgebra.printer import  Format, xpdf
Format()
from sympy import symbols, sin, pi, latex, Array, permutedims
from galgebra.ga import Ga

print(r'\bm{\mbox{Base manifold (three dimensional)}}')
print(r'\bm{\mbox{Metric tensor (cartesian coordinates - norm = False)}}')
from sympy import cos, sin, symbols
g3coords = (x,y,z) = symbols('x y z')
g3 = Ga('ex ey ez', g = [1,1,1], coords = g3coords,norm=False) # Create g3
(e_x,e_y,e_z) = g3.mv()

print('g =',g3.g)
print('\\')

print(r'\bm{\mbox{Two dimensioanal submanifold - Unit sphere}}')
print(r'\text{Basis not normalised}')

sp2coords = (theta, phi) = symbols('theta phi', real = True)
sp2param = [sin(theta)*cos(phi), sin(theta)*sin(phi), cos(theta)]

sp2 = g3.sm(sp2param, sp2coords, norm = False) # submanifold

(etheta, ephi) = sp2.mv() # sp2 basis vectors
(rtheta, rphi) = sp2.mvr() # sp2 reciprocal basis vectors

sp2grad = sp2.grad

sph_map = [1, theta, phi]  # Coordinate map for sphere of r = 1
print(r'(\theta,\phi)\rightarrow (r,\theta,\phi) = ', latex(sph_map))

(etheta, ephi) = sp2.mv()
print(r'e_\theta | e_\theta = ', etheta|etheta)
print(r'e_\phi | e_\phi = ', ephi|ephi)

print('g =',sp2.g)
print(r'\text{g\_inv = }', latex(sp2.g_inv))

#print(r'\text{signature = ', latex(sp2.signature()))

Cf1 = sp2.Christoffel_symbols(mode=1)
Cf1 = permutedims(Array(Cf1), (2, 0, 1))
print(r'\text{Christoffel symbols of the first kind: }')
print(r'\Gamma_{1, \alpha, \beta} = ', latex(Cf1[0, :, :]), r'\quad', r'\Gamma_{2, \alpha, \beta} = ', latex(Cf1[1, :, :]))

Cf2 = sp2.Christoffel_symbols(mode=2)
Cf2 = permutedims(Array(Cf2), (2, 0, 1))
print(r'\text{Christoffel symbols of the second kind: }')
print(r'\Gamma^{1}_{\phantom{1,}\alpha, \beta} = ', latex(Cf2[0, :, :]), r'\quad', r'\Gamma^{2}_{\phantom{2,}\alpha, \beta} = ', latex(Cf2[1, :, :]))

F = sp2.mv('F','vector',f=True) #scalar function)
f = sp2.mv('f','scalar',f=True) #vector function)
print('grad = ', sp2grad)
print('grad f =',sp2.grad * f)
print('F =',F)
print('grad F = ',sp2.grad * F)
print('\\')

print(r'\mbox{One dimensioanal submanifold}')
print(r'\mbox{Basis not normalised}')

cir_th = phi = symbols('phi',real = True)
cir_map = [pi/8, phi]
print(r'(\phi)\rightarrow (\theta,\phi) = ', latex(cir_map))

cir1d = sp2.sm( cir_map , (cir_th,), norm = False) # submanifold

cir1dgrad = cir1d.grad

(ephi) = cir1d.mv()
print(r'e_\phi | e_\phi = ', latex(ephi[0] | ephi[0]))
print('g = ', latex(cir1d.g))

h = cir1d.mv('h','scalar',f= True)

H = cir1d.mv('H','vector',f= True)

print('grad = ', cir1dgrad)
print(r'\nabla h = ', (cir1d.grad * h).simplify())
print('H =', H)
print(r'\nabla H = ', (cir1d.grad * H).simplify())
print('\\' )

# xpdf(paper=(9,10))
xpdf(paper=(9,10),pdfprog=None)


# Autores: Mercedes Miranda
#          El nombre de tu amigo
# Fecha; 9 de septiembre de 2019
# Metodo Newton Raphson

import sympy as sym
#se define el simbolo variable principal, no mover
x = sym.Symbol('x')
#se define la funcipon para desarrollar el metodo
#funcion es la función en terminos de sympy, trataré de agregar un apendice
#de funciones posibles que se pueden agregar
#x0 es el valor semilla, tomar las consideraciones necesarias para ello
#errorRelativo es el criterio de paro dado por el usuario
#es importante tener en cuenta que las iteraciones seran pocas debido a la
#eficacia del metodo
def newton(funcion,x0,errorRelativo,digitos):
    #se calcula la derivada y la division de f(x)/f'(x)
    derivada   = funcion.diff(x,1)
    diferencia=funcion/derivada
    #se muestra la division
    print("h(x) = f(x)/f'(x)")
    print("h(x) = ",diferencia)

    #variable auxiliar para las iteraciones
    iteracion=0
    banderaConvergencia=0
    #listas para guardar la información
    valoresAproximados=[x0]
    valoresErrorRelativo=['-']
    criterioDeConvergencia=[round(CriterioConvergencia(funcion,x0),digitos)]

    while(True):
        #se calcula la aproximacion
        xAnterior=x0
        a=diferencia.subs(x,x0)
        a=x0-a.evalf()
        x0=round(a,digitos)
        valoresAproximados.append(x0)

        #si estamos en una iteracion diferente a la primera, se calcula el
        #error relativo
        if(iteracion!=0):
            errorR=round((abs((x0-xAnterior)/x0))*100,digitos)
            valoresErrorRelativo.append(errorR)
            #si se cumple el criterio de paro, sale de las iteraciones
            if(errorR<errorRelativo):
                break
        #se calcula el valor de convergencia
        valorG=round(CriterioConvergencia(funcion,x0),digitos)
        criterioDeConvergencia.append(valorG)
        #se checa si el valor cumple o no lo especificado
        if abs(valorG)>1:
            banderaConvergencia=1
            break

        #se incrementa en 1 el contador de iteraciones
        iteracion+=1
    #se retornan todas las listas y el valor de la bandera
    return valoresAproximados,valoresErrorRelativo,criterioDeConvergencia,banderaConvergencia

#esta función evalua para el criterio de convergencia en cada aproximación generada y regresa
#el valor de la evaluación de |G'(x)|
def CriterioConvergencia(funcion,x0):
    #se calculan las dos derivadas
    derivada   = funcion.diff(x,1)
    derivadaDos = funcion.diff(x,2)
    #se genera a la función G'(x)
    G=(funcion*derivadaDos)/(derivada**2)
    #se evalua para la raiz encontrada
    a=G.subs(x,x0)
    a=a.evalf()

    return a

#solo genera la grafica de la funcion
def Grafica(funcion):
    grafica=sym.plotting.plot(funcion,show=True)

#función maestra
def main():
    #la función se muestra aquí
    funcion=sym.cos(x)-(x**2)
    #si quieres que el usuario ingrese la función, descomenta las lineas entre
    #signos de suma y comenta la linea de arriba de "funcion"
    #recuerda usar la documentación para saber como ingresar ciertas funciones
    #++++++++++++++++++++++++++
    #funcion=input("ingresa la función en terminos de x")
    #++++++++++++++++++++++++++

    #grafica
    Grafica(funcion)
    #criterio de paro
    errorRelativo=float(input("introduce el porcentaje de error para el criterio de paro "))
    #valor semilla
    semilla=float(input("ingresa el valor semilla "))
    #cantidad de digitos al redondear
    digitos=int(input("cantidad de digitos de redondeo, hasta 8 permitidos "))

    #se generan todos los valores necesarios para desplegar la tabla
    valores,errores,criterios,bandera=newton(funcion,semilla,errorRelativo,digitos)
    #se imprimen los valores
    print("{:2}|{:^12}|{:^12}|{:^12}|{:^12}".format("I","xi","xi+1","ErrorR","CritConv"))
    for i in range(len(valores)-1):
        print("{:2}|{:12}|{:12}|{:12}|{:12}".format(i+1,valores[i],valores[i+1],errores[i],criterios[i]))
    #hasta acá es donde se checa el criterio de convergencia
    if bandera==1 :
        print("hubo un problema con el criterio de paro en la ultima iteración, quizá por esto terminó el proceso")
        print("revisa tu valor semilla e intenta con otro ")

main()


# -*- coding: utf-8 -*-
"""
Created on Tue Mar 26 19:46:14 2019

@author: Nate
"""

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

'''
Determine the value of π to ≈ 14 digits by solving for the root of the equation

f(x) = cos(x) = 0

using the second order Newton’s method. The exact solution is

    x∗ = π/2, so that π = 2x∗.

Use the initial guess of x = 1.5, corresponds to a guess of π ≈ 3. How many
iterations are needed to achieve 14 digits? Repeat the calculation with initial guesses
x = 1, x = 0.5 and x = 0.25.
'''

def f(guess):
    return(np.cos(guess))

def df(guess):
    return(-np.sin(guess))

guess = [1.5,1,.5,.25]

for i in range(len(guess)):
    for val in range(4):
        nextguess = guess[i] - f(guess[i])/df(guess[i])
        guess[i] = nextguess

    if i == 3:
        answer = guess[i]
        #print(guess[i])
        #print(answer)
        print(i, ": " , (3/2)*np.pi-answer)
        if abs((3/2)*np.pi-answer) < 10**(-12):
            print('True')
    else:
        answer = 2*guess[i]
        print(i, ": " , np.pi-answer)
        if abs(np.pi-answer) < 10**(-12):
            print('True')

print(10**(-10))


# Chapter 10

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

## Review

So far we have been working with distributions of only one variable.  In this notebook we'll take a step toward multivariate distributions, starting with two variables.

We'll use cross-tabulation to compute a **joint distribution**, then use the joint distribution to compute **conditional distributions** and **marginal distributions**.

We will re-use `pmf_from_seq`, which I introduced in a previous notebook.

def pmf_from_seq(seq):
    """Make a PMF from a sequence of values.

    seq: sequence

    returns: Series representing a PMF
    """
    pmf = pd.Series(seq).value_counts(sort=False).sort_index()
    pmf /= pmf.sum()
    return pmf

## Cross tabulation

To understand joint distributions, I'll start with cross tabulation.  And to demonstrate cross tabulation, I'll generate a dataset of colors and fruits.

Here are the possible values.

colors = ['red', 'yellow', 'green']
fruits = ['apple', 'banana', 'grape']

And here's a random sample of 100 fruits.

np.random.seed(2)
fruit_sample = np.random.choice(fruits, 100, replace=True)

We can use `pmf_from_seq` to compute the distribution of fruits.

pmf_fruit = pmf_from_seq(fruit_sample)
pmf_fruit

And here's what it looks like.

pmf_fruit.plot.bar(color='C0')

plt.ylabel('Probability')
plt.title('Distribution of fruit');

Similarly, here's a random sample of colors.

color_sample = np.random.choice(colors, 100, replace=True)

Here's the distribution of colors.

pmf_color = pmf_from_seq(color_sample)
pmf_color

And here's what it looks like.

pmf_color.plot.bar(color='C1')

plt.ylabel('Probability')
plt.title('Distribution of colors');

Looking at these distributions, we know the proportion of each fruit, ignoring color, and we know the proportion of each color, ignoring fruit type.

But if we only have the distributions and not the original data, we don't know how many apples are green, for example, or how many yellow fruits are bananas.

We can compute that information using `crosstab`, which computes the number of cases for each combination of fruit type and color.

xtab = pd.crosstab(color_sample, fruit_sample,
                   rownames=['color'], colnames=['fruit'])
xtab

The result is a DataFrame with colors along the rows and fruits along the columns.

## Heatmap

The following function plots a cross tabulation using a pseudo-color plot, also known as a heatmap.

It represents each element of the cross tabulation with a colored square, where the color corresponds to the magnitude of the element.

The following function generates a heatmap using the Matplotlib function `pcolormesh`:

def plot_heatmap(xtab):
    """Make a heatmap to represent a cross tabulation.

    xtab: DataFrame containing a cross tabulation
    """

    plt.pcolormesh(xtab)

    # label the y axis
    ys = xtab.index
    plt.ylabel(ys.name)
    locs = np.arange(len(ys)) + 0.5
    plt.yticks(locs, ys)

    # label the x axis
    xs = xtab.columns
    plt.xlabel(xs.name)
    locs = np.arange(len(xs)) + 0.5
    plt.xticks(locs, xs)

    plt.colorbar()
    plt.gca().invert_yaxis()

plot_heatmap(xtab)

## Joint Distribution

A cross tabulation represents the "joint distribution" of two variables, which is a complete description of two distributions, including all of the conditional distributions.

If we normalize `xtab` so the sum of the elements is 1, the result is a joint PMF:

joint = xtab / xtab.to_numpy().sum()
joint

Each column in the joint PMF represents the conditional distribution of color for a given fruit.

For example, we can select a column like this:

col = joint['apple']
col

If we normalize it, we get the conditional distribution of color for a given fruit.

col / col.sum()

Each row of the cross tabulation represents the conditional distribution of fruit for each color.

If we select a row and normalize it, like this:

row = xtab.loc['red']
row / row.sum()

The result is the conditional distribution of fruit type for a given color.

## Conditional distributions

The following function takes a joint PMF and computes conditional distributions:

def conditional(joint, name, value):
    """Compute a conditional distribution.

    joint: DataFrame representing a joint PMF
    name: string name of an axis
    value: value to condition on

    returns: Series representing a conditional PMF
    """
    if joint.columns.name == name:
        cond = joint[value]
    elif joint.index.name == name:
        cond = joint.loc[value]
    return cond / cond.sum()

The second argument is a string that identifies which axis we want to select; in this example, `'fruit'` means we are selecting a column, like this:

conditional(joint, 'fruit', 'apple')

And `'color'` means we are selecting a row, like this:

conditional(joint, 'color', 'red')

**Exercise:** Compute the conditional distribution of color for bananas.  What is the probability that a banana is yellow?

# Solution goes here

# Solution goes here

## Marginal distributions

Given a joint distribution, we can compute the unconditioned distribution of either variable.

If we sum along the rows, which is axis 0, we get the distribution of fruit type, regardless of color.

joint.sum(axis=0)

If we sum along the columns, which is axis 1, we get the distribution of color, regardless of fruit type.

joint.sum(axis=1)

These distributions are called "[marginal](https://en.wikipedia.org/wiki/Marginal_distribution#Multivariate_distributions)" because of the way they are often displayed.  We'll see an example later.

As we did with conditional distributions, we can write a function that takes a joint distribution and computes the marginal distribution of a given variable:

def marginal(joint, name):
    """Compute a marginal distribution.

    joint: DataFrame representing a joint PMF
    name: string name of an axis

    returns: Series representing a marginal PMF
    """
    if joint.columns.name == name:
        return joint.sum(axis=0)
    elif joint.index.name == name:
        return joint.sum(axis=1)

Here's the marginal distribution of fruit.

pmf_fruit = marginal(joint, 'fruit')
pmf_fruit

And the marginal distribution of color:

pmf_color = marginal(joint, 'color')
pmf_color

The sum of the marginal PMF is the same as the sum of the joint PMF, so if the joint PMF was normalized, the marginal PMF should be, too.

joint.to_numpy().sum()

pmf_color.sum()

However, due to floating point error, the total might not be exactly 1.

pmf_fruit.sum()

**Exercise:** The following cells load the data from the General Social Survey that we used in Notebooks 1 and 2.

# Load the data file
import os

if not os.path.exists('gss_bayes.csv'):
    !wget https://github.com/AllenDowney/BiteSizeBayes/raw/master/gss_bayes.csv

gss = pd.read_csv('gss_bayes.csv', index_col=0)

As an exercise, you can use this data to explore the joint distribution of two variables:

* `partyid` encodes each respondent's political affiliation, that is, the party the belong to.  [Here's the description](https://gssdataexplorer.norc.org/variables/141/vshow).

* `polviews` encodes their political alignment on a spectrum from liberal to conservative.  [Here's the description](https://gssdataexplorer.norc.org/variables/178/vshow).

The values for `partyid` are

```
0	Strong democrat
1	Not str democrat
2	Ind,near dem
3	Independent
4	Ind,near rep
5	Not str republican
6	Strong republican
7	Other party
```

The values for `polviews` are:

```
1	Extremely liberal
2	Liberal
3	Slightly liberal
4	Moderate
5	Slightly conservative
6	Conservative
7	Extremely conservative
```

Make a cross tabulation of `gss['partyid']` and `gss['polviews']` and normalize it to make a joint PMF.

# Solution goes here

Use `plot_heatmap` to display a heatmap of the joint distribution.  What patterns do you notice?

plot_heatmap(joint2)

plt.xlabel('polviews')
plt.title('Joint distribution of polviews and partyid');

Use `marginal` to compute the marginal distributions of `partyid` and `polviews`, and plot the results.

# Solution goes here

# Solution goes here

Use `conditional` to compute the conditional distribution of `partyid` for people who identify themselves as "Extremely conservative" (`polviews==7`).  How many of them are "strong Republicans" (`partyid==6`)?

# Solution goes here

Use `conditional` to compute the conditional distribution of `polviews` for people who identify themselves as "Strong Democrat" (`partyid==0`).  How many of them are "Extremely liberal" (`polviews==1`)?

# Solution goes here

## Review

In this notebook we started with cross tabulation, which we normalized to create a joint distribution, which describes the distribution of two (or more) variables and all of their conditional distributions.

We used heatmaps to visualize cross tabulations and joint distributions.

Then we defined `conditional` and `marginal` functions that take a joint distribution and compute conditional and marginal distributions for each variables.

As an exercise, you had a chance to apply the same methods to explore the relationship between political alignment and party affiliation using data from the General Social Survey.

You might have noticed that we did not use Bayes's Theorem in this notebook.  [In the next notebook](https://colab.research.google.com/github/AllenDowney/BiteSizeBayes/blob/master/11_faceoff.ipynb) we'll take the ideas from this notebook and apply them Bayesian inference.


#Implementing and in neural network
import pandas as pd

# TODO: Set weight1, weight2, and bias
weight1 = 1
weight2 = 1
bias = -2

# DON'T CHANGE ANYTHING BELOW
# Inputs and outputs
test_inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
correct_outputs = [False, False, False, True]
outputs = []

# Generate and check output
for test_input, correct_output in zip(test_inputs, correct_outputs):
    linear_combination = weight1 * test_input[0] + weight2 * test_input[1] + bias
    output = int(linear_combination >= 0)
    is_correct_string = 'Yes' if output == correct_output else 'No'
    outputs.append([test_input[0], test_input[1], linear_combination, output, is_correct_string])

# Print output
num_wrong = len([output[4] for output in outputs if output[4] == 'No'])
output_frame = pd.DataFrame(outputs, columns=['Input 1', '  Input 2', '  Linear Combination', '  Activation Output', '  Is Correct'])
if not num_wrong:
    print('Nice!  You got it all correct.\n')
else:
    print('You got {} wrong.  Keep trying!\n'.format(num_wrong))
print(output_frame.to_string(index=False))

#Implementing not in nueral network
import pandas as pd

# TODO: Set weight1, weight2, and bias
weight1 = -1
weight2 = -2
bias = 1

# DON'T CHANGE ANYTHING BELOW
# Inputs and outputs
test_inputs = [(0, 0), (0, 1), (1, 0), (1, 1)]
correct_outputs = [True, False, True, False]
outputs = []

# Generate and check output
for test_input, correct_output in zip(test_inputs, correct_outputs):
    linear_combination = weight1 * test_input[0] + weight2 * test_input[1] + bias
    output = int(linear_combination >= 0)
    is_correct_string = 'Yes' if output == correct_output else 'No'
    outputs.append([test_input[0], test_input[1], linear_combination, output, is_correct_string])

# Print output
num_wrong = len([output[4] for output in outputs if output[4] == 'No'])
output_frame = pd.DataFrame(outputs, columns=['Input 1', '  Input 2', '  Linear Combination', '  Activation Output', '  Is Correct'])
if not num_wrong:
    print('Nice!  You got it all correct.\n')
else:
    print('You got {} wrong.  Keep trying!\n'.format(num_wrong))
print(output_frame.to_string(index=False))

#Implementing perceptron algorithm
import numpy as np
# Setting the random seed, feel free to change it and see different solutions.
np.random.seed(42)

def stepFunction(t):
    if t >= 0:
        return 1
    return 0

def prediction(X, W, b):
    return stepFunction((np.matmul(X,W)+b)[0])

# TODO: Fill in the code below to implement the perceptron trick.
# The function should receive as inputs the data X, the labels y,
# the weights W (as an array), and the bias b,
# update the weights and bias W, b, according to the perceptron algorithm,
# and return W and b.

def perceptronStep(X, y, W, b, learn_rate = 0.001):
    for i in range(len(X)):
        y_hat = prediction(X[i],W,b)
        if y[i]-y_hat == 1:
            W[0] += X[i][0]*learn_rate
            W[1] += X[i][1]*learn_rate
            b += learn_rate
        elif y[i]-y_hat == -1:
            W[0] -= X[i][0]*learn_rate
            W[1] -= X[i][1]*learn_rate
            b -= learn_rate
    return W, b

# This function runs the perceptron algorithm repeatedly on the dataset,
# and returns a few of the boundary lines obtained in the iterations,
# for plotting purposes.
# Feel free to play with the learning rate and the num_epochs,
# and see your results plotted below.
def trainPerceptronAlgorithm(X, y, learn_rate = 0.01, num_epochs = 50):
    x_min, x_max = min(X.T[0]), max(X.T[0])
    y_min, y_max = min(X.T[1]), max(X.T[1])
    W = np.array(np.random.rand(2,1))
    b = np.random.rand(1)[0] + x_max
    # These are the solution lines that get plotted below.
    boundary_lines = []
    for i in range(num_epochs):
        # In each epoch, we apply the perceptron step.
        W, b = perceptronStep(X, y, W, b, learn_rate)
        boundary_lines.append((-W[0]/W[1], -b/W[1]))
    return boundary_lines

#IMplementing softmax
import numpy as np

# Write a function that takes as input a list of numbers, and returns
# the list of values given by the softmax function.
def softmax(x):
    y=np.exp(x)
    sumre=sum(y)
    result = []
    for i in x:
        result.append(np.exp(i)/sumre)
    return result

#Implementing cross entrophy
import numpy as np

def cross_entropy(Y, P):
    Y = np.float_(Y)
    P = np.float_(P)
    return -np.sum(Y * np.log(P) + (1 - Y) * np.log(1 - P))


from matplotlib import pyplot as plt
from scipy.cluster.hierarchy import dendrogram, linkage
import matplotlib.pyplot as plt
import numpy as np
X = np.array([[1, 3], [4, 2], [5, 6], [3, 1], [7, 2], [3, 3], ])

#Plotting Data
labels = range(1, 7)
plt.figure(figsize=(7, 3))
plt.subplots_adjust(bottom=0.1)
plt.scatter(X[:, 0], X[:, 1], label='True Position')

for label, x, y in zip(labels, X[:, 0], X[:, 1]):
    plt.annotate(
        label,
        xy=(x, y), xytext=(-3, 3),
        textcoords='offset points', ha='right', va='bottom')
plt.show()

## Use Hierarchical Algorithm Single Method
linked = linkage(X, 'single')
labelList = range(1, 7)
plt.figure(figsize=(7, 3))
dendrogram(linked, orientation='top',labels=labelList,distance_sort='descending',show_leaf_counts=True)
plt.show()

# Use Hierarchical Algorithm Complete Method
linked = linkage(X, 'complete')
labelList = range(1, 7)
plt.figure(figsize=(7, 3))
dendrogram(linked, orientation='top', labels=labelList,
           distance_sort='descending', show_leaf_counts=True)
plt.show()

# Use Hierarchical Algorithm Average Method
linked = linkage(X, 'average')
labelList = range(1, 7)
plt.figure(figsize=(7, 3))
dendrogram(linked, orientation='top', labels=labelList,
           distance_sort='descending', show_leaf_counts=True)
plt.show()

# Use Hierarchical Algorithm Ward Method
linked = linkage(X, 'ward')
labelList = range(1, 7)
plt.figure(figsize=(7, 3))
dendrogram(linked, orientation='top', labels=labelList,
           distance_sort='descending', show_leaf_counts=True)
plt.show()

# Use Hierarchical Algorithm Centroid Method
linked = linkage(X, 'centroid')
labelList = range(1, 7)
plt.figure(figsize=(7, 3))
dendrogram(linked, orientation='top', labels=labelList,
           distance_sort='descending', show_leaf_counts=True)
plt.show()

# Use Hierarchical Algorithm Median Method
linked = linkage(X, 'median')
labelList = range(1, 7)
plt.figure(figsize=(7, 3))
dendrogram(linked, orientation='top', labels=labelList,
           distance_sort='descending', show_leaf_counts=True)
plt.show()


import numpy as np
import scipy
import sympy
from numpy import linalg as lg
from numpy.linalg import solve
from numpy.linalg import eig
from scipy.integrate import quad

# Question 1
# MC question
# Part a. C. Not defined because the columns don't match the # of entries
# Part b. C.

# Question 2
# Write the system as a vector equation and then as a matrix equation
'''
8x1 + x2 - 3x3 = 8
     2x2 + 3x3 = 0

# Vector Equation

x1 8 + x2 1 + x3 -3 = 8
   0      2       3   0

# Matrix equation

          [x1]
[8 1 -3]  [x2] = [8]
[0 2  3]  [x3]   [0]

'''

# Question 3
# Part a builds an augmented matrix

# Part b, Solve:

a1 = np.array([[1, 4, -3], [-3, -5, 2], [4, 2, 6]])
b1 = np.array([[-1, 17, -20]])

print(a1, '\n\n', b1.T, '\n')

x1 = np.linalg.solve(a1, b1.T)
print(x1, '\n') # Correct

# Question 4
# MC question
# Answer: D. Yes. When the given vectors are written as the columns of a # matrix A, A has a pivot position in every row.

# Question 5
# The coefficients of the equation are the answer
# Answer:
# X1 = -4
# X2 = -4

# Question 6
# Multiple Choice
# Part A
# Could a set of three vectors in set of real numbers R4 span all of set of real numbers R4?
# No. The matrix A whose columns are the three vectors has four rows. To have # a pivot in each row, A would have to have at least four columns (one for # each pivot.)

# Part B
# Could a set of n vectors in set of real numbers Rm span all of set of real # numbers Rm when n is less than m? Explain. Choose the correct answer
# No. The matrix A whose columns are the n vectors has m rows. To have a pivot # in each row, A would have to have at least m columns (one for each pivot.)

# Question 7
# The system has a nontrivial solution

# Question 8
a2 = np.array([[4, 4, 8], [-8, -8, -16], [0, -3, 9], [0, 0, 0]])
b2 = np.array([[0, 0, 0]])

c1 = sympy.Matrix(a2).rref()
print("The Matrix in RREF is: ", c1)

# Simplifies to:
'''
x1 +    5x3 = 0
    x2 -3x3 = 0
          0 = 0

x1 = -5x3
x2 =  3x3

    x1   -5x3   -5
x = x2 =  3x3 =  3
    x3     x3    1

x = x3[-5, 3, 1]
'''

# Question 9
# Part a
# Avh = A(w - p)
# Part b
# A(u + v) = Au + Av
# Part c
# Avh = Aw - Ap
# Part d
# Avh = b - b
#Part e
# Avh = 0

# Question 10
# Part A
# A is a 3x3 matrix with 3 pivot positions
# Does the equation Ax = 0 have a nontrivial solution?
# Answer: No

# Part B
# Does the equation Axequalsb have at least one solution for every possible b?
# Answer: Yes
'''
With three pivot positions, A has a pivot position in each of its three rows, and therefore, for each b in set of real numbers R cubed, the equation Axequalsb has a solution.'''

# Question 11
# The vector equation has only the trivial solution, so the
# vectors are linearly independent.

# Question 12
# Find the value for which the vectors are linearly dependent
'''
[ 2 6 -3]
[-2 -8 3]
[ 6 17 h]

Add row 1 to row 2:
[ 2 6 -3]
[ 0 -2 0]
[ 6 17 h]

Add -3 x row 1 to row 3:
[ 2 6 -3]
[ 0 -2 0]
[ 0 -1 h+9]

Add -0.5 x row 2 to row 3:
[ 2 6 -3]
[ 0 -2 0]
[0 0 h+9]

x3 = -9
'''

# Question 13
'''
If A is an m times n matrix, then the columns of A are linearly independent if and only if A has n pivot columns.

The columns of a matrix A are linearly independent if and only if Axequals0 has no free variables, meaning every variable is a basic variable, that is, if and only if every column of A is a pivot column
'''

# Question 14
# Multiple Choice

import numpy,from numpy,import scipy,from scipy,import sympy


import numpy as np

from isaac.stats import sigmoid

__all__ = [
    "LinearRegression",
    "LogisticRegression"
]

class LinearRegression(object):

    def __init__(self, weights):
        self.weights = weights
        self.dimension = len(weights)

    @classmethod
    def from_dimension(cls, dimension, value=None, dtype=None):
        dtype = dtype or np.float64
        if value is None:
            weights = np.ones(dimension, dtype)
        else:
            weights = np.zeros(dimension, dtype) + value
        return cls(weights)

    def predict(self, x):
        return np.dot(x, self.weights)

    def costs(self, x, y):
        '''
        Measuring the Mean Squared Error over the training set.
        '''
        return np.mean(np.power(np.dot(x, self.weights) - y, 2))

    def cost_derivative(self, x, y):
        costs = (np.dot(x, self.weights) - y)
        derivatives = np.mean(x.T * costs, axis=1)
        return derivatives

    def update_weights(self, new_weights):
        self.weights = new_weights

class LogisticRegression(LinearRegression):

    def predict(self, x):
        return 1 / (1 + np.exp((- np.dot(x, self.weights))))

    def one_cost(self, x, y):
        prediction = self.predict(Xs)
        cost = (
            (- y) * math.log(prediction) - (1 - y) * (math.log(1 - prediction))
            )
        return cost

    def costs(self, x, y):
        predictions = self.predict(x)
        # -log(self.predict(X) if Y == 1)
        # -log(1 - self.predict(X) if Y == 0)
        return np.mean(
            (- y) * np.log(predictions) - (1 - y) * (np.log(1 - predictions)))

    def cost_derivative(self, x, y):
        costs = (self.predict(x) - y)
        derivatives = np.mean(x.T * costs, axis=1)
        return derivatives

    # TODO I suspect this implementation here below
    # is not a clever one.
    def accuracy(self, x, y):
        '''
        return
            float, percentage of accuracy
        '''
        total = len(y)
        predictions = self.predict(x)
        # step the values
        predictions[predictions > 0.5] = 1
        predictions[predictions < 0.5] = 0
        # stats the accuracy
        validations = (predictions == y)
        return validations.mean()


# -*- coding: utf-8 -*-
"""
Created on Sun Apr 14 20:15:48 2019

@author:    Manuel Martinez
@project:   Mathematics for Machine Learning.
@purpose:   Week 4 programming assignment on principal component analysis.
"""
# PACKAGE: DO NOT EDIT THIS CELL
import numpy as np
import timeit

# PACKAGE: DO NOT EDIT THIS CELL
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')

from load_data import load_mnist
MNIST = load_mnist()
images, labels = MNIST['data'], MNIST['target']

# %%===========================================================================
# main
# =============================================================================
plt.figure(figsize=(4, 4))
plt.imshow(images[0].reshape(28, 28), cmap='gray')

# %%===========================================================================
# 1. PCA
# =============================================================================

# GRADED FUNCTION: DO NOT EDIT THIS LINE
def normalize(X):
    """Normalize the given dataset X
    Args:
        X: ndarray, dataset

    Returns:
        (Xbar, mean, std): tuple of ndarray, Xbar is the normalized dataset
        with mean 0 and standard deviation 1; mean and std are the
        mean and standard deviation respectively.

    Note:
        You will encounter dimensions where the standard deviation is
        zero, for those when you do normalization the normalized data
        will be NaN. Handle this by setting using `std = 1` for those
        dimensions when doing normalization.
    """
    mu = X.mean(axis=0)
    std = np.std(X, axis=0)
    std_filled = std.copy()
    std_filled[std == 0] = 1
    Xbar = (X - mu) / std_filled
    return Xbar, mu, std

def eig(S):
    """Compute the eigenvalues and corresponding eigenvectors
        for the covariance matrix S.
    Args:
        S: ndarray, covariance matrix

    Returns:
        (eigvals, eigvecs): ndarray, the eigenvalues and eigenvectors

    Note:
        the eigenvals and eigenvecs should be sorted in descending
        order of the eigen values
    """
    eigvals, eigvecs = np.linalg.eig(S)
    desc_order = np.argsort(eigvals)[::-1]
    eigvals = eigvals[desc_order]
    eigvecs = eigvecs[:, desc_order]
    return (eigvals, eigvecs)

def projection_matrix(B):
    """Compute the projection matrix onto the space spanned by `B`
    Args:
        B: ndarray of dimension (D, M), the basis for the subspace

    Returns:
        P: the projection matrix
    """
    P = B @ np.linalg.inv(B.T @ B) @ B.T
    return P

def PCA(X, num_components):
    """
    Args:
        X: ndarray of size (N, D), where D is the dimension of the data,
           and N is the number of datapoints
        num_components: the number of principal components to use.
    Returns:
        X_reconstruct: ndarray of the reconstruction
        of X from the first `num_components` principal components.
    """
    N, D = X.shape
    S = X.T @ X / N
    _, B = eig(S)
    B = B[:, :num_components]
    P = projection_matrix(B)
    X_reconstruct = (P @ X.T).T
    return X_reconstruct

# %% Some preprocessing of the data
NUM_DATAPOINTS = 1000
X = (images.reshape(-1, 28 * 28)[:NUM_DATAPOINTS]) / 255.
Xbar, mu, std = normalize(X)

# %% take a look at how squared distance changes with n_dimensions
for num_component in range(1, 20):
    from sklearn.decomposition import PCA as SKPCA
    # We can compute a standard solution given by scikit-learn's
    # implementation of PCA
    pca = SKPCA(n_components=num_component, svd_solver='full')
    sklearn_reconst = pca.inverse_transform(pca.fit_transform(Xbar))
    reconst = PCA(Xbar, num_component)
    np.testing.assert_almost_equal(reconst, sklearn_reconst)
    print(np.square(reconst - sklearn_reconst).sum())

# %%
def mse(predict, actual):
    """Helper function for computing the mean squared error (MSE)"""
    return np.square(predict - actual).sum(axis=1).mean()

loss = []
reconstructions = []
# iterate over different number of principal components, and compute the MSE
for num_component in range(1, 100):
    reconst = PCA(Xbar, num_component)
    error = mse(reconst, Xbar)
    reconstructions.append(reconst)
#    print('n = {:d}, reconstruction_error = {:f}'.format(num_component, error))
    loss.append((num_component, error))

reconstructions = np.asarray(reconstructions)
# "unnormalize" the reconstructed image
reconstructions = reconstructions * std + mu
loss = np.asarray(loss)

# %%
fig, ax = plt.subplots()
ax.plot(loss[:, 0], loss[:, 1])
ax.axhline(100, linestyle='--', color='r', linewidth=2)
ax.xaxis.set_ticks(np.arange(1, 100, 5))
ax.set(xlabel='num_components', ylabel='MSE',
       title='MSE vs number of principal components')

# %%
# GRADED FUNCTION: DO NOT EDIT THIS LINE
# PCA for high dimensional datasets
def PCA_high_dim(X, n_components):
    """Compute PCA for small sample size but high-dimensional features.
    Args:
        X: ndarray of size (N, D), where D is the dimension of the sample,
           and N is the number of samples
        num_components: the number of principal components to use.
    Returns:
        X_reconstruct: (N, D) ndarray. the reconstruction
        of X from the first `num_components` pricipal components.
    """
    N, D = X.shape
    _, V = eig(X @ X.T)
    U = (X.T @ V)[:, :n_components]
    P = projection_matrix(U)
    X_reconstruct = (P @ X.T).T

    return X_reconstruct

# %%
np.testing.assert_almost_equal(PCA(Xbar, 2), PCA_high_dim(Xbar, 2))
print('A-OK')

# %%
def time(f, repeat=10):
    times = []
    for _ in range(repeat):
        start = timeit.default_timer()
        f()
        stop = timeit.default_timer()
        times.append(stop-start)
    return np.mean(times), np.std(times)

# %%
times_mm0 = []
times_mm1 = []

# iterate over datasets of different size
for datasetsize in np.arange(4, 784, step=20):
    XX = Xbar[:datasetsize] # select the first `datasetsize` samples in the dataset
    # record the running time for computing X.T @ X
    mu, sigma = time(lambda : XX.T @ XX)
    times_mm0.append((datasetsize, mu, sigma))

    # record the running time for computing X @ X.T
    mu, sigma = time(lambda : XX @ XX.T)
    times_mm1.append((datasetsize, mu, sigma))

times_mm0 = np.asarray(times_mm0)
times_mm1 = np.asarray(times_mm1)

fig, ax = plt.subplots()
ax.set(xlabel='size of dataset', ylabel='running time')
bar = ax.errorbar(times_mm0[:, 0], times_mm0[:, 1], times_mm0[:, 2], label="$X^T X$ (PCA)", linewidth=2)
ax.errorbar(times_mm1[:, 0], times_mm1[:, 1], times_mm1[:, 2], label="$X X^T$ (PCA_high_dim)", linewidth=2)
ax.legend();

%time Xbar.T @ Xbar
%time Xbar @ Xbar.T
# Put this here so that our output does not show result of computing `Xbar @ Xbar.T`
pass

# %%
times0 = []
times1 = []

# iterate over datasets of different size
for datasetsize in np.arange(4, 784, step=100):
    XX = Xbar[:datasetsize]
    npc = 2
    mu, sigma = time(lambda : PCA(XX, npc), repeat=10)
    times0.append((datasetsize, mu, sigma))

    mu, sigma = time(lambda : PCA_high_dim(XX, npc), repeat=10)
    times1.append((datasetsize, mu, sigma))

times0 = np.asarray(times0)
times1 = np.asarray(times1)

fig, ax = plt.subplots()
ax.set(xlabel='number of datapoints', ylabel='run time')
ax.errorbar(times0[:, 0], times0[:, 1], times0[:, 2], label="PCA", linewidth=2)
ax.errorbar(times1[:, 0], times1[:, 1], times1[:, 2], label="PCA_high_dim", linewidth=2)
ax.legend();

%time PCA(Xbar, 2)
%time PCA_high_dim(Xbar, 2)
pass


#T# radicals are simplified like regular expressions, but their variables must be defined in the set of real numbers for the simplification to work

#T# to simplify expressions with radicals, the sympy package is used
import sympy

#T# create the variable, it must be created as a variable in the set of real numbers using the real kwarg with a value of True
x = sympy.Symbol('x', real = True) # x

#T# the sqrt function of the sympy package returns the square root of its argument
expr1 = sympy.sqrt(x) # sqrt(x)

#T# the Pow function of the sympy package returns its first argument raised to the power of the second
num1 = 2
expr1 = sympy.Pow(x, num1) # x**2

#T# the powsimp function simplifies expressions with exponents
x = sympy.Symbol('x')     # x
y = sympy.Symbol('y')     # y
a = sympy.Rational('1/2') # 1/2
b = sympy.Rational('1/3') # 1/3
expr1 = x**a * x**b                        # x**(5/6)
expr2 = x**a * y**a                        # sqrt(x)*sqrt(y)
expr3 = sympy.powsimp(expr1)               # x**(5/6)
expr3 = sympy.powsimp(expr2, force = True) # sqrt(x*y) #| the force kwarg must be set to True to show the simplification


import numpy as np

###
##
#

#
##
###

t = 45.0  # t: beam thickness (y) [m]
h = 30.0  # h: beam height (z) [m]
rho = 160.0  # density of steel [kg/mˆ3]
E = 2.861e8  # E: Young's modulus of steel [N/mˆ2]
nu = 3/10  # nu: Poisson's ratio

G = E/2/(1+nu)  # G: Shear modulus [N/mˆ2]
l = 3.0  # l: beam element length
A = t*h  # beam area [mˆ2]
ASy = 5/6*A
ASz = 5/6*A  # effective area of shear
Iy = 1/12*h**3*t
Iz = 1/12*t**3*h  # second moments of area [mˆ4]
Ip = 1/12*t*h*(h**2+t**2)  # polar moment of inertia [mˆ4]
It = min([h, t])**3 * max([h, t])/7  # torsion constant [mˆ4]
Py = 12*E*Iz/(G*ASy*l**2)
Pz = 12*E*Iy/(G*ASz*l**2)

length = l
m_const = rho * A * l

###
##
#

#
##
###

def get_mass_matrix_ver1():
    '''
    VERSION 1

    NOTE: checking out alternative implementation
    according to https://mediatum.ub.tum.de/doc/1072355/file.pdf
    description and implementation seems correct
    NOTE: find out where the formulation for the mass comes from, stiffness seems standard
    '''
    M11 = np.zeros((6, 6))
    M11[0][0] = 1/3
    M11[1][1] = 13/35 + 6*Iz/(5*A*l**2)
    M11[2][2] = 13/35 + 6*Iy/(5*A*l**2)
    M11[3][3] = Ip/(3*A)
    M11[4][4] = l**2/105 + 2*Iy/(15*A)
    M11[5][5] = l**2/105 + 2*Iz/(15*A)
    M11[5][1] = 11*l/210 + Iz/(10*A*l)
    M11[1][5] = M11[5][1]
    M11[4][2] = -11*l/210-Iy/(10*A*l)
    M11[2][4] = M11[4][2]

    M22 = -M11 + 2*np.diag(np.diag(M11))

    M21 = np.zeros((6, 6))
    M21[0][0] = 1/6
    M21[1][1] = 9/70-6*Iz/(5*A*l**2)
    M21[2][2] = 9/70-6*Iy/(5*A*l**2)
    M21[3][3] = Ip/(6*A)
    M21[4][4] = -l**2/140-Iy/(30*A)
    M21[5][5] = -l**2/140-Iz/(30*A)
    M21[5][1] = -13*l/420 + Iz/(10*A*l)
    M21[1][5] = -M21[5][1]
    M21[4][2] = 13*l/420-Iy/(10*A*l)
    M21[2][4] = -M21[4][2]

    # mass values for one level
    length = l
    m_const = rho * A * length

    m_el = np.zeros((2*6, 2*6))
    # upper left
    m_el[0:6, 0:6] += m_const * M11
    # lower left
    m_el[6:12, 0:6] += m_const * M21
    # upper right
    m_el[0:6, 6:12] += m_const * np.transpose(M21)
    # lower right
    m_el[6:12, 6:12] += m_const * M22

    return m_el

def get_mass_matrix_ver2():
    '''
    VERSION 2

    NOTE: from http://homes.civil.aau.dk/jc/FemteSemester/Beams3D.pdf
    seems to be a typo in 1-105 and 1-106 as a division with l**3 instead of l**3 should take place
    implemented mass matrices similar to the stiffness one
    '''
    # define component-wise to have enable better control for various optimization parameters

    # axial inertia - along axis x - here marked as x
    m_x = m_const / 6.0
    m_x_11 = 2.
    m_x_12 = 1.
    m_el_x = m_x * np.array([[m_x_11, m_x_12],
                             [m_x_12, m_x_11]])
    # torsion inertia - around axis x - here marked as alpha - a
    m_a = m_const * Ip/A / 6.0
    m_a_11 = 2
    m_a_12 = 1
    m_el_a = m_a * np.array([[m_a_11, m_a_12],
                             [m_a_12, m_a_11]])
    # bending - inertia along axis y, rotations around axis z - here marked as gamma - g
    m_yg = m_const / 420
    #
    m_yg_11 = 156.
    m_yg_12 = 22. * length
    m_yg_13 = 54.
    m_yg_14 = -13. * length
    #
    m_yg_22 = 4 * length ** 2
    m_yg_23 = - m_yg_14
    m_yg_24 = -3 * length ** 2
    #
    m_yg_33 = m_yg_11
    m_yg_34 = -m_yg_12
    #
    m_yg_44 = m_yg_22
    #
    m_el_yg = m_yg * np.array([[m_yg_11, m_yg_12, m_yg_13, m_yg_14],
                               [m_yg_12, m_yg_22, m_yg_23, m_yg_24],
                               [m_yg_13, m_yg_23, m_yg_33, m_yg_34],
                               [m_yg_14, m_yg_24, m_yg_34, m_yg_44]])

    # bending - inertia along axis z, rotations around axis y - here marked as beta - b
    m_zb = m_const / 420
    #
    m_zb_11 = 156.
    m_zb_12 = -22. * length
    m_zb_13 = 54.
    m_zb_14 = 13. * length
    #
    m_zb_22 = 4. * length ** 2
    m_zb_23 = -m_zb_14
    m_zb_24 = -3 * length ** 2
    #
    m_zb_33 = m_zb_11
    m_zb_34 = - m_zb_12
    #
    m_zb_44 = m_zb_22
    #
    m_el_zb = m_zb * np.array([[m_zb_11, m_zb_12, m_zb_13, m_zb_14],
                               [m_zb_12, m_zb_22, m_zb_23, m_zb_24],
                               [m_zb_13, m_zb_23, m_zb_33, m_zb_34],
                               [m_zb_14, m_zb_24, m_zb_34, m_zb_44]])

    # assemble all components
    m_el = np.array([[m_el_x[0][0], 0., 0., 0., 0., 0.,                 m_el_x[0][1], 0., 0., 0., 0., 0.],
                     [0., m_el_yg[0][0], 0., 0., 0., m_el_yg[0][1],
                         0., m_el_yg[0][2], 0., 0., 0., m_el_yg[0][3]],
                     [0., 0., m_el_zb[0][0], 0., m_el_zb[0][1], 0.,
                         0., 0., m_el_zb[0][2], 0., m_el_zb[0][3], 0.],
                     [0., 0., 0., m_el_a[0][0], 0., 0.,
                         0., 0., 0., m_el_a[0][1], 0., 0.],
                     [0., 0., m_el_zb[0][1], 0., m_el_zb[1][1], 0.,
                         0., 0., m_el_zb[1][2], 0., m_el_zb[1][3], 0.],
                     [0., m_el_yg[0][1], 0., 0., 0., m_el_yg[1][1],
                         0., m_el_yg[1][2], 0., 0., 0., m_el_yg[1][3]],

                     [m_el_x[1][0], 0., 0., 0., 0., 0.,
                         m_el_x[1][1], 0., 0., 0., 0., 0.],
                     [0., m_el_yg[0][2], 0., 0., 0., m_el_yg[1][2],
                      0., m_el_yg[2][2], 0., 0., 0., m_el_yg[2][3]],
                     [0., 0., m_el_zb[0][2], 0., m_el_zb[1][2], 0.,
                      0., 0., m_el_zb[2][2], 0., m_el_zb[2][3], 0.],
                     [0., 0., 0., m_el_a[1][0], 0., 0.,
                      0., 0., 0., m_el_a[1][1], 0., 0.],
                     [0., 0., m_el_zb[0][3], 0., m_el_zb[1][3], 0.,
                      0., 0., m_el_zb[2][3], 0., m_el_zb[3][3], 0.],
                     [0., m_el_yg[0][3], 0., 0., 0., m_el_yg[1][3],     0., m_el_yg[2][3], 0., 0., 0., m_el_yg[3][3]]])

    return m_el

def get_mass_matrix_ver3():
    '''
    VERSION 3

    NOTE: from Appendix A - Straight Beam Element Matrices - page 228
    https://link.springer.com/content/pdf/bbm%3A978-3-319-56493-7%2F1.pdf

    '''

    # define component-wise to have enable better control for various optimization parameters

    # axial inertia - along axis x - here marked as x
    m_x = m_const / 6.0
    m_x_11 = 2.
    m_x_12 = 1.
    m_el_x = m_x * np.array([[m_x_11, m_x_12],
                             [m_x_12, m_x_11]])
    # torsion inertia - around axis x - here marked as alpha - a
    m_a = m_const * Ip/A / 6.0
    m_a_11 = 2
    m_a_12 = 1
    m_el_a = m_a * np.array([[m_a_11, m_a_12],
                             [m_a_12, m_a_11]])

    # bending - inertia along axis y, rotations around axis z - here marked as gamma - g
    # translation
    m_yg = m_const / 210 / (1+Py)**2
    #
    m_yg_11 = 70*Py**2 + 147*Py + 78
    m_yg_12 = (35*Py**2 + 77*Py + 44) * length / 4
    m_yg_13 = 35*Py**2 + 63*Py + 27
    m_yg_14 = -(35*Py**2 + 63*Py + 26) * length / 4
    #
    m_yg_22 = (7*Py**2 + 14*Py + 8) * length ** 2 / 4
    m_yg_23 = - m_yg_14
    m_yg_24 = -(7*Py**2 + 14*Py + 6) * length ** 2 / 4
    #
    m_yg_33 = m_yg_11
    m_yg_34 = -m_yg_12
    #
    m_yg_44 = m_yg_22
    #
    m_el_yg_trans = m_yg * np.array([[m_yg_11, m_yg_12, m_yg_13, m_yg_14],
                                     [m_yg_12, m_yg_22, m_yg_23, m_yg_24],
                                     [m_yg_13, m_yg_23, m_yg_33, m_yg_34],
                                     [m_yg_14, m_yg_24, m_yg_34, m_yg_44]])
    # rotation
    m_yg = rho*Iz / 30 / (1+Py)**2 / length
    #
    m_yg_11 = 36
    m_yg_12 = -(15*Py-3) * length
    m_yg_13 = -m_yg_11
    m_yg_14 = m_yg_12
    #
    m_yg_22 = (10*Py**2 + 5*Py + 4) * length ** 2
    m_yg_23 = - m_yg_12
    m_yg_24 = (5*Py**2 - 5*Py - 1) * length ** 2
    #
    m_yg_33 = m_yg_11
    m_yg_34 = - m_yg_12
    #
    m_yg_44 = m_yg_22
    #
    m_el_yg_rot = m_yg * np.array([[m_yg_11, m_yg_12, m_yg_13, m_yg_14],
                                   [m_yg_12, m_yg_22, m_yg_23, m_yg_24],
                                   [m_yg_13, m_yg_23, m_yg_33, m_yg_34],
                                   [m_yg_14, m_yg_24, m_yg_34, m_yg_44]])

    # sum up translation and rotation
    m_el_yg = m_el_yg_trans + m_el_yg_rot

    # bending - inertia along axis z, rotations around axis y - here marked as beta - b
    # translation
    m_zb = m_const / 210 / (1+Pz)**2
    #
    m_zb_11 = 70*Pz**2 + 147*Pz + 78
    m_zb_12 = -(35*Pz**2 + 77*Pz + 44) * length / 4
    m_zb_13 = 35*Pz**2 + 63*Pz + 27
    m_zb_14 = (35*Pz**2 + 63*Pz + 26) * length / 4
    #
    m_zb_22 = (7*Pz**2 + 14*Pz + 8) * length ** 2 / 4
    m_zb_23 = -m_zb_14
    m_zb_24 = -(7*Pz**2 + 14*Pz + 6) * length ** 2 / 4
    #
    m_zb_33 = m_zb_11
    m_zb_34 = - m_zb_12
    #
    m_zb_44 = m_zb_22
    #
    m_el_zb_trans = m_zb * np.array([[m_zb_11, m_zb_12, m_zb_13, m_zb_14],
                                     [m_zb_12, m_zb_22, m_zb_23, m_zb_24],
                                     [m_zb_13, m_zb_23, m_zb_33, m_zb_34],
                                     [m_zb_14, m_zb_24, m_zb_34, m_zb_44]])
    # rotation
    m_zb = rho*Iy / 30 / (1+Pz)**2 / length
    #
    m_zb_11 = 36
    m_zb_12 = (15*Pz-3) * length
    m_zb_13 = -m_zb_11
    m_zb_14 = m_zb_12
    #
    m_zb_22 = (10*Pz**2 + 5*Pz + 4) * length ** 2
    m_zb_23 = -m_zb_12
    m_zb_24 = (5*Pz**2 - 5*Pz - 1) * length ** 2
    #
    m_zb_33 = m_zb_11
    m_zb_34 = -m_zb_12
    #
    m_zb_44 = m_zb_22
    #
    m_el_zb_rot = m_zb * np.array([[m_zb_11, m_zb_12, m_zb_13, m_zb_14],
                                   [m_zb_12, m_zb_22, m_zb_23, m_zb_24],
                                   [m_zb_13, m_zb_23, m_zb_33, m_zb_34],
                                   [m_zb_14, m_zb_24, m_zb_34, m_zb_44]])

    # sum up translation and rotation
    m_el_zb = m_el_zb_trans + m_el_zb_rot

    # assemble all components
    m_el = np.array([[m_el_x[0][0], 0., 0., 0., 0., 0.,                 m_el_x[0][1], 0., 0., 0., 0., 0.],
                     [0., m_el_yg[0][0], 0., 0., 0., m_el_yg[0][1],
                         0., m_el_yg[0][2], 0., 0., 0., m_el_yg[0][3]],
                     [0., 0., m_el_zb[0][0], 0., m_el_zb[0][1], 0.,
                         0., 0., m_el_zb[0][2], 0., m_el_zb[0][3], 0.],
                     [0., 0., 0., m_el_a[0][0], 0., 0.,
                         0., 0., 0., m_el_a[0][1], 0., 0.],
                     [0., 0., m_el_zb[0][1], 0., m_el_zb[1][1], 0.,
                         0., 0., m_el_zb[1][2], 0., m_el_zb[1][3], 0.],
                     [0., m_el_yg[0][1], 0., 0., 0., m_el_yg[1][1],
                         0., m_el_yg[1][2], 0., 0., 0., m_el_yg[1][3]],

                     [m_el_x[1][0], 0., 0., 0., 0., 0.,
                         m_el_x[1][1], 0., 0., 0., 0., 0.],
                     [0., m_el_yg[0][2], 0., 0., 0., m_el_yg[1][2],
                      0., m_el_yg[2][2], 0., 0., 0., m_el_yg[2][3]],
                     [0., 0., m_el_zb[0][2], 0., m_el_zb[1][2], 0.,
                      0., 0., m_el_zb[2][2], 0., m_el_zb[2][3], 0.],
                     [0., 0., 0., m_el_a[1][0], 0., 0.,
                      0., 0., 0., m_el_a[1][1], 0., 0.],
                     [0., 0., m_el_zb[0][3], 0., m_el_zb[1][3], 0.,
                      0., 0., m_el_zb[2][3], 0., m_el_zb[3][3], 0.],
                     [0., m_el_yg[0][3], 0., 0., 0., m_el_yg[1][3],     0., m_el_yg[2][3], 0., 0., 0., m_el_yg[3][3]]])

    return m_el

# mass values for one level
version1_m_el = get_mass_matrix_ver1()
print("\n VERSION1 - m_el")
print(np.array2string(version1_m_el, precision=3,
                      separator=',', suppress_small=True))

version2_m_el = get_mass_matrix_ver2()
print("\n VERSION2 - m_el")
print(np.array2string(version2_m_el, precision=3,
                      separator=',', suppress_small=True))

version3_m_el = get_mass_matrix_ver3()
print("\n VERSION3 - m_el")
print(np.array2string(version3_m_el, precision=3,
                      separator=',', suppress_small=True))

# print("\n NORM - between m_el_ versions:")
# print(np.linalg.norm(version1_m_el - version3_m_el))
# print()
# wait = input("check...")

###
##
#

#
##
###

def get_stiffness_matrix_ver1():
    '''
    VERSION 1

    NOTE: checking out alternative implementation
    according to https://mediatum.ub.tum.de/doc/1072355/file.pdf
    description and implementation seems correct
    NOTE: find out where the formulation for the mass comes from, stiffness seems standard
    '''

    K11 = np.zeros((6, 6))
    K11[0][0] = E*A/l
    K11[1][1] = 12*E*Iz/(l**3*(1+Py))
    K11[2][2] = 12*E*Iy/(l**3*(1+Pz))
    K11[3][3] = G*It/l
    K11[4][4] = (4+Pz)*E*Iy/(l*(1+Pz))
    K11[5][5] = (4+Py)*E*Iz/(l*(1+Py))
    K11[1][5] = 6*E*Iz/(l**2*(1+Py))
    K11[5][1] = K11[1][5]
    K11[2][4] = -6*E*Iy/(l**2*(1+Pz))
    K11[4][2] = K11[2][4]

    K22 = -K11 + 2*np.diag(np.diag(K11))

    K21 = K11 - 2*np.diag(np.diag(K11))
    K21[4][4] = (2-Pz)*E*Iy/(l*(1+Pz))
    K21[5][5] = (2-Py)*E*Iz/(l*(1+Py))
    K21[1][5] = -K21[5][1]
    K21[2][4] = -K21[4][2]

    k_el = np.zeros((2*6, 2*6))
    # upper left
    k_el[0:6, 0:6] += K11
    # lower left
    k_el[6:12, 0:6] += K21
    # upper right
    k_el[0:6, 6:12] += np.transpose(K21)
    # lower right
    k_el[6:12, 6:12] += K22

    return k_el

def get_stiffness_matrix_ver2():
    '''
    VERSION 2

    NOTE: from http://homes.civil.aau.dk/jc/FemteSemester/Beams3D.pdf
    seems to be a typo in 1-105 and 1-106 as a division with l**3 instead of l**3 should take place
    implemented mass matrices similar to the stiffness one
    '''

    # stifness values for one level
    # define component-wise to have enable better control for various optimization parameters

    length = l
    # axial stiffness - along axis x - here marked as x
    k_x = E*A/l
    k_x_11 = 1.0
    k_x_12 = -1.0
    k_el_x = k_x * np.array([[k_x_11, k_x_12],
                             [k_x_12, k_x_11]])
    # torsion stiffness - around axis x - here marked as alpha - a
    k_a = G*It/l  # G*K/l
    k_a_11 = 1.0
    k_a_12 = -1.0
    k_el_a = k_a * np.array([[k_a_11, k_a_12],
                             [k_a_12, k_a_11]])
    # bending - displacement along axis y, rotations around axis z - here marked as gamma - g
    beta_yg = Py
    k_yg = E*Iz/(1+beta_yg)/l**3
    #
    k_yg_11 = 12.
    k_yg_12 = 6. * length
    k_yg_13 = -k_yg_11
    k_yg_14 = k_yg_12
    #
    k_yg_22 = (4.+beta_yg) * length ** 2
    k_yg_23 = -k_yg_12
    k_yg_24 = (2-beta_yg) * length ** 2
    #
    k_yg_33 = k_yg_11
    k_yg_34 = -k_yg_12
    #
    k_yg_44 = k_yg_22
    #
    k_el_yg = k_yg * np.array([[k_yg_11, k_yg_12, k_yg_13, k_yg_14],
                               [k_yg_12, k_yg_22, k_yg_23, k_yg_24],
                               [k_yg_13, k_yg_23, k_yg_33, k_yg_34],
                               [k_yg_14, k_yg_24, k_yg_34, k_yg_44]])

    # bending - displacement along axis z, rotations around axis y - here marked as beta - b
    beta_zb = Pz
    k_zb = E*Iy/(1+beta_zb)/l**3
    #
    k_zb_11 = 12.
    k_zb_12 = -6. * length
    k_zb_13 = -12.
    k_zb_14 = k_zb_12
    #
    k_zb_22 = (4.+beta_zb) * length ** 2
    k_zb_23 = -k_zb_12
    k_zb_24 = (2-beta_zb) * length ** 2
    #
    k_zb_33 = k_zb_11
    k_zb_34 = - k_zb_12
    #
    k_zb_44 = k_zb_22
    #
    k_el_zb = k_zb * np.array([[k_zb_11, k_zb_12, k_zb_13, k_zb_14],
                               [k_zb_12, k_zb_22, k_zb_23, k_zb_24],
                               [k_zb_13, k_zb_23, k_zb_33, k_zb_34],
                               [k_zb_14, k_zb_24, k_zb_34, k_zb_44]])

    # assemble all components
    k_el = np.array([[k_el_x[0][0], 0., 0., 0., 0., 0.,                 k_el_x[0][1], 0., 0., 0., 0., 0.],
                     [0., k_el_yg[0][0], 0., 0., 0., k_el_yg[0][1],
                         0., k_el_yg[0][2], 0., 0., 0., k_el_yg[0][3]],
                     [0., 0., k_el_zb[0][0], 0., k_el_zb[0][1], 0.,
                         0., 0., k_el_zb[0][2], 0., k_el_zb[0][3], 0.],
                     [0., 0., 0., k_el_a[0][0], 0., 0.,
                         0., 0., 0., k_el_a[0][1], 0., 0.],
                     [0., 0., k_el_zb[0][1], 0., k_el_zb[1][1], 0.,
                         0., 0., k_el_zb[1][2], 0., k_el_zb[1][3], 0.],
                     [0., k_el_yg[0][1], 0., 0., 0., k_el_yg[1][1],
                         0., k_el_yg[1][2], 0., 0., 0., k_el_yg[1][3]],

                     [k_el_x[1][0], 0., 0., 0., 0., 0.,
                         k_el_x[1][1], 0., 0., 0., 0., 0.],
                     [0., k_el_yg[0][2], 0., 0., 0., k_el_yg[1][2],
                      0., k_el_yg[2][2], 0., 0., 0., k_el_yg[2][3]],
                     [0., 0., k_el_zb[0][2], 0., k_el_zb[1][2], 0.,
                      0., 0., k_el_zb[2][2], 0., k_el_zb[2][3], 0.],
                     [0., 0., 0., k_el_a[1][0], 0., 0.,
                      0., 0., 0., k_el_a[1][1], 0., 0.],
                     [0., 0., k_el_zb[0][3], 0., k_el_zb[1][3], 0.,
                      0., 0., k_el_zb[2][3], 0., k_el_zb[3][3], 0.],
                     [0., k_el_yg[0][3], 0., 0., 0., k_el_yg[1][3],     0., k_el_yg[2][3], 0., 0., 0., k_el_yg[3][3]]])

    return k_el

version1_k_el = get_stiffness_matrix_ver1()
print("\n VERSION1 - k_el")
print(np.array2string(version1_k_el, precision=3,
                      separator=',', suppress_small=True))

version2_k_el = get_stiffness_matrix_ver2()
print("\n VERSION2 - k_el")
print(np.array2string(version2_k_el, precision=3,
                      separator=',', suppress_small=True))

# print("\n NORM - k_el versions:")
# print(np.linalg.norm(version1_k_el - version2_k_el))
# print()
# wait = input("check...")


import numpy as np
import matplotlib.pyplot as plt
import scipy.sparse as sp
import scipy.sparse.linalg as la
from functools import partial
import time

def make_L(Nx, Ny):
    Dx = sp.diags((Nx-1)*[1])
    Dx += sp.diags((Nx-2)*[-1],-1)
    rowx = sp.csr_matrix((1,Nx-1))
    rowx[0,-1] = -1
    Dx = sp.vstack((Dx, rowx))
    Lx = Dx.transpose().dot(Dx)

    Dy = sp.diags((Ny-1)*[1])
    Dy += sp.diags((Ny-2)*[-1],-1)
    rowy = sp.csr_matrix((1,Ny-1))
    rowy[0,-1] = -1
    Dy = sp.vstack((Dy, rowy))
    Ly = Dy.transpose().dot(Dy)
    return sp.kronsum(Lx,Ly)

def discretize(x_d, y_d, h):
    nx = int(x_d/h)
    ny = int(y_d/h)
    return make_L(nx,ny)/h/h

def get_grid(x_d, y_d, h):
    grid = np.mgrid[h:y_d:h, h:x_d:h]
    return (grid[1,:,:], grid[0,:,:])

def FE_step(u0, A, dt):
    return (sp.identity(A.shape[0])+(A)*dt)._mul_vector(u0)

def BE_step(u0, A, dt):
    return la.spsolve((sp.identity(A.shape[0])-(A)*dt), u0)

def stable_dt(h):
    return (h**2)/4

# Unsterady solver interface
def unsteady_solver(u0, A, dt, T, saves, method="FE", stabilizer=None):
    stepper = FE_step
    if method == "BE":
        stepper = BE_step
    if stabilizer is not None and method == "FE":
        dt = stabilizer()

    t = 0
    un = u0
    us = []
    if 0 in save_points:
        us.append(u0)
    while t <= T:
        t += dt
        un = stepper(un, A, dt)
        for s in saves:
            if abs(s-t) <= dt/2:
                us.append(un)

    return us

def initial(xx, yy):
    a = -5
    return np.reshape(np.exp(a*np.ones(xx.shape)*(np.square(xx-2*np.ones(xx.shape))+np.square(yy-2*np.ones(yy.shape)))), xx.shape[0]*xx.shape[1])

# Domain is [0,x] x [0,4]
x = 4
y = 4
h = 0.02
# specify  dt
dt = 0.015

grid = get_grid(x,y,h)
reshaper = lambda u: np.reshape(u, [grid[0].shape[0], grid[0].shape[1]])[::-1,:]
L = -1 * discretize(x,y,h)

# Specify where to save the solution
save_points = [0, 0.045, 0.09, 0.15]

print(stable_dt(h))

start = time.time()
# Remove the stabilizer arguemnt if you want to use dt as the timestep for FE
# Set method to either "FE" or "BE"
uno = unsteady_solver(initial(*grid), L, dt, 0.15, save_points, method="FE", stabilizer = lambda : stable_dt(h))
print(time.time()-start)
start = time.time()
dos = unsteady_solver(initial(*grid), L, dt, 0.15, save_points, method="BE")
print(time.time()-start)

# Normalization
mx = max(np.max(np.array(uno)), np.max(np.array(dos)))
mn = max(np.min(np.array(uno)), np.min(np.array(dos)))

# Plotting
fig = plt.figure()
plt.subplot(2,5,1)
plt.imshow(reshaper(uno[0]), vmin=mn, vmax=mx)
plt.title("t = 0s")
plt.subplot(2,5,2)
plt.imshow(reshaper(uno[1]), vmin=mn, vmax=mx)
plt.title("t = 0.045s")
plt.subplot(2,5,3)
plt.imshow(reshaper(uno[2]), vmin=mn, vmax=mx)
plt.title("t = 0.09s")
plt.subplot(2,5,4)
plt.imshow(reshaper(uno[3]), vmin=mn,  vmax=mx)
plt.title("t = 0.15s")

plt.subplot(2,5,6)
plt.imshow(reshaper(dos[0]), vmin=mn, vmax=mx)
plt.subplot(2,5,7)
plt.imshow(reshaper(dos[1]), vmin=mn, vmax=mx)
plt.subplot(2,5,8)
plt.imshow(reshaper(dos[2]), vmin=mn, vmax=mx)
plt.subplot(2,5,9)
im = plt.imshow(reshaper(dos[3]), vmin=mn, vmax=mx)

cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
fig.colorbar(im, cax=cbar_ax)

plt.show()


import numpy as np

def map_feature(x1, x2, degree=6):
    """
        Maps the two input features to quadratic features used in logistic regression.

        Parameters
        ----------
        x1 : array_like
            Shape (m, 1), containing one feature.

        x2 : array_like
            Shape (m, 1), containing a second feature of the same size.

        degree : int, optional
            The polynomial degree.

        Returns
        -------
        : array_like
            A matrix of m rows, and columns depending on degree of polynomial.
    """

    if x1.ndim > 0:
        out = [np.ones(x1.shape[0])]
    else:
        out = [np.ones(1)]

    for i in range(1, degree+1):
        for j in range(i + 1):
            out.append((x1 ** (i - j)) * (x2 ** j))

    if x1.ndim > 0:
        return np.stack(out, axis=1)
    else:
        return np.array(out)


def kmeans(puntos,k):
    import numpy
    #1. Generar k centroides
    mean=[0,0]
    cov=[[1,0],[0,1]]
    centroides=numpy.random.multivariate_normal(mean,cov,k)
    print(centroides)

    iteracion=0
    #centroides_nuevos=centroides+10
    while True:
        #2.Asignar al centroide más cercano
        kmin=[]
        for x in puntos:
            distance=[]
            for y in centroides:
                distance.append(numpy.linalg.norm(x-y))
            kmin.append(numpy.argmin(distance))

        #3. Calcular la distancia promedio: nuevos centroides
        kmin=numpy.asarray(kmin)

        list_k=list(range(k))
        centroides_nuevos=numpy.zeros((k,2))
        for n in list_k:
            centroides_nuevos[n]=(numpy.mean(puntos[kmin==n,:], axis=0))

        iteracion=iteracion+1
        print('Iteración:')
        print(iteracion)
        print('Centroides:')
        print(centroides_nuevos)
        if (numpy.linalg.norm(centroides-centroides_nuevos)>1*10**-5):
            centroides=centroides_nuevos
        else:
            break

    return centroides_nuevos
    #El resultado final varía dependiendo de la semilla


import sympy as sym
from sympy.abc import s, t, x, y, z
import numpy as np
from sympy.integrals import inverse_laplace_transform
import matplotlib.pyplot as plt

# Define inputs
# First step (up) starts at 1 sec
U1 = 2 / s * sym.exp(-s)
# Ramp (down) starts at 3 sec
U2 = -1 / s ** 2 * sym.exp(-3 * s)
# Ramp completes at 5 sec
U3 = 1 / s ** 2 * sym.exp(-5 * s)

# Transfer function
G = 5 * (s + 1) / (s + 3) ** 2

# Calculate responses
Y1 = G * U1
Y2 = G * U2
Y3 = G * U3

# Inverse Laplace Transform
u1 = inverse_laplace_transform(U1, s, t)
u2 = inverse_laplace_transform(U2, s, t)
u3 = inverse_laplace_transform(U3, s, t)
y1 = inverse_laplace_transform(Y1, s, t)
y2 = inverse_laplace_transform(Y2, s, t)
y3 = inverse_laplace_transform(Y3, s, t)
print('y1')
print(y1)

# generate data for plot
tm = np.linspace(0, 8, 100)
us = np.zeros(len(tm))
ys = np.zeros(len(tm))

# substitute numeric values for u and y
for u in [u1, u2, u3]:
    for i in range(len(tm)):
        us[i] += u.subs(t, tm[i])
for y in [y1, y2, y3]:
    for i in range(len(tm)):
        ys[i] += y.subs(t, tm[i])

# plot results
plt.figure()
plt.plot(tm, us, label='u(t)')
plt.plot(tm, ys, label='y(t)')
plt.legend()
plt.xlabel('Time')
plt.show()


'''Implementation of the quantile loss function'''

from typing import Sequence
import numpy as np

def quantile_loss(predictions: Sequence[float],
                  targets: Sequence[float],
                  quantile: float) -> float:
    '''Quantile loss function.

    Args:
        predictions (sequence of floats):
            Model predictions, of shape [n_samples,].
        targets (sequence of floats):
            Target values, of shape [n_samples,].
        quantile (float):
            The quantile we are seeking. Must be between 0 and 1.

    Returns:
        float: The quantile loss.
    '''
    # Convert inputs to NumPy arrays
    target_arr = np.asarray(targets)
    prediction_arr = np.asarray(predictions)

    # Compute the residuals
    res = target_arr - prediction_arr

    # Compute the mean quantile loss
    loss = np.mean(np.maximum(res, np.zeros_like(res)) * quantile +
                   np.maximum(-res, np.zeros_like(res)) * (1 - quantile))

    # Ensure that loss is of type float and return it
    return float(loss)

def smooth_quantile_loss(predictions: Sequence[float],
                         targets: Sequence[float],
                         quantile: float,
                         alpha: float = 0.4) -> float:
    '''The smooth quantile loss function from [1].

    Args:
        predictions (sequence of floats):
            Model predictions, of shape [n_samples,].
        targets (sequence of floats):
            Target values, of shape [n_samples,].
        quantile (float):
            The quantile we are seeking. Must be between 0 and 1.
        alpha (float, optional):
            Smoothing parameter. Defaults to 0.4.

    Returns:
        float: The smooth quantile loss.

    Sources:
        [1]: Songfeng Zheng (2011). Gradient Descent Algorithms for
             Quantile Regression With Smooth Approximation. International
             Journal of Machine Learning and Cybernetics.
    '''
    # Convert inputs to NumPy arrays
    target_arr = np.asarray(targets)
    prediction_arr = np.asarray(predictions)

    # Compute the residuals
    residuals = target_arr - prediction_arr

    # Compute the smoothened mean quantile loss
    loss = (quantile * residuals +
            alpha * np.log(1 + np.exp(-residuals / alpha)))

    # Ensure that loss is of type float and return it
    return float(loss.mean())


import numpy as np

def lcs(a,b,c,m,n,p,t):
    if m<0 or n<0 or p<0:
        # print(t)
        return 0
    else:
        if a[m]==b[n]==c[p]:
            t=f'{t}{a[m]}'
            return 1+lcs(a,b,c,m-1,n-1,p-1,t)
        else:
            return max(
                lcs(a,b,c,m-1,n,p,t),
                lcs(a,b,c,m,n-1,p,t),
                lcs(a,b,c,m,n,p-1,t)
            )

def lcsdp(a,b,c):
    m=len(a)
    n=len(b)
    p=len(c)
    t=""
    dp=np.zeros((m+1,n+1,p+1))
    # print(dp)

    for i in range(m+1):
        for j in range(n+1):
            for k in range(p+1):
                if i==0 or j==0 or k==0:
                    continue

                if a[i-1]==b[j-1]==c[k-1]:
                    dp[i][j][k]=dp[i-1][j-1][k-1]+1
                else:
                    dp[i][j][k] = max(
                        dp[i-1][j][k],
                        dp[i][j-1][k],
                        dp[i][j][k-1]
                    )

    print(dp[m][n][p])
    print(i,m,j,n,k,p)

    str=""

    while i>0 and j>0 and k>0:
        # print(i,j,k)
        if a[i-1]==b[j-1]==c[k-1]:
            print(a[i-1])
            str=f'{a[i-1]}{str}'
            i-=1
            j-=1
            k-=1
            # print(i,j,k)
        else:
            n1=dp[i-1][j][k]
            n2=dp[i][j-1][k]
            n3=dp[i][j][k-1]
            if n1>=n2 and n1>=n3:
                i-=1
            elif n2>=n1 and n2>=n3:
                j-=1
            elif n3>=n1 and n3>=n2:
                k-=1
            else:
                print('##',n1,n2,n3)

    print(str)

if __name__ == "__main__":
    a="6662220542630314443712"
    # a="ACHCGGTCGAGTTGCGCGGAHAGCCLGGCCGAA"
    b="00078321207413782377777"
    # b="GTCXGTTCGDGAATGFCGTTGCTOTGTAAA"
    c="6664664567861104057425"
    # c="AKLGTCGTCGGAAUIOGCCGGCCGAA"

    m=len(a)
    n=len(b)
    p=len(c)
    t=""
    print(m)
    # test=lcs(a,b,c,m-1,n-1,p-1,t)
    lcsdp(a,b,c)


#! /usr/bin/env python3
# -*- coding: utf-8 -*-

# from https://docs.scipy.org/doc/numpy-dev/user/quickstart.html

import numpy as np
a = np.arange(15).reshape(3, 5)

print("a=",a)
print("\ta.shape =",a.shape)
print("\ta.ndim =",a.ndim)
print("\ta.dtype.name =",a.dtype.name)
print("\ta.itemsize =",a.itemsize)
print("\ta.size =",a.size)
print("\ttype(a) =",type(a))

b = np.array([6, 7, 8])
print("b=",b)
print("\ttype(b)=",type(b))
print("\tb.dtype.name =",b.dtype.name)

print(np.array([(1.5,2,3), (4,5,6)]))

print(np.zeros( (3,4) ))

print(np.ones( (2,3,4), dtype=np.int32 ))

print(np.empty( (2,3) ))

x = np.linspace( 0, 2*np.pi, 5 )
f = np.sin(x)    # np.exp, np.sqrt, nop.cos, etc
print("x=",x)
print("sin(x)=",f)

# Arithmetic operators on arrays apply elementwise

A = np.array( [[1,1], [0,1]] )
B = np.array( [[2,0], [3,4]] )
print("A=", A)
print("B=", B)
print("A*B=", A*B)                         # elementwise product
print("A.dot(B)=", A.dot(B))     # matrix product

b = np.arange(12).reshape(3,4)
print("b=", b)
print("b.sum(axis=0) =", b.sum(axis=0))
print("b.min(axis=1) =", b.min(axis=1))
print("b.max(axis=0) =", b.max(axis=0))

# indexing / slicing

a = np.arange(10)+3
print("a =", a)
print("a[2] =", a[2])
print("a[2:4] =", a[2:4])     # renvoie une "view", c a d une ref
print("a[0:6:2] =", a[0:6:2])  #i=>j par pas de k
print("a[:] =",a[:])
print("a[-1] =",a[-1])

b = np.fromfunction(lambda x,y: 10*x+y,(5,4),dtype=int) #creation par fct
print("b =", b)
print("b[1:3, : ] =", b[1:3, : ])
print("b[2] =", b[2])  # 3ieme ligne

# iteration
for element in b.flat:
    print(element)

# shape

print("A.T = ", A.T)
a= np.arange(12)
print("a=", a)
a.reshape(3,4) # a n'est PAS modifié => renvoie un objet
print("a=", a)
a.resize(3,4) # a est modifié
print("a=", a)

# views/copies
d = a           # d n'est qu'une ref vers a (1 seul objet numpy)
d = a.view()    # d est un nouvel objet numpy qui pointe vers les mêmes données
                # les slices fonctionnent comme ca
d = a.copy()    # d est indep de "a"


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

import matplotlib.pyplot as plt
from mpl_format.axes.axis_utils import new_axes
from numpy import linspace
from probability.distributions import PERT

x = linspace(0, 100, 1001)

def plot_wikipedia_pdfs():
    """
    https://en.wikipedia.org/wiki/PERT_distribution#/media/
    File:PERT_pdf_examples.jpg
    """
    ax = new_axes(width=10, height=10)
    PERT(0, 10, 100).pdf().plot(x=x, color='blue', ax=ax)
    PERT(0, 50, 100).pdf().plot(x=x, color='orange', ax=ax)
    PERT(0, 70, 100).pdf().plot(x=x, color='gray', ax=ax)
    ax.set_ylim(0, 0.03)
    ax.set_title('Probability density function')
    ax.legend(loc='upper center')
    plt.show()

def plot_wikipedia_cdfs():
    """
    https://en.wikipedia.org/wiki/PERT_distribution#/media/
    File:PERT_cdf_examples.jpg
    """
    ax = new_axes(width=10, height=10)
    PERT(0, 10, 100).cdf().plot(x=x, color='blue', ax=ax)
    PERT(0, 50, 100).cdf().plot(x=x, color='orange', ax=ax)
    PERT(0, 70, 100).cdf().plot(x=x, color='gray', ax=ax)
    ax.set_ylim(0, 1)
    ax.set_title('Cumulative density function')
    ax.legend(loc='upper center')
    plt.show()

if __name__ == '__main__':

    plot_wikipedia_pdfs()
    plot_wikipedia_cdfs()


"""
  Name     : c2_09_bsCall.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""

def bsCall(S,X,T,r,sigma):
    from scipy import log,exp,sqrt,stats
    d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
    d2 = d1-sigma*sqrt(T)
    return S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2)


# Reconstruct SVD
from numpy import array
from numpy import diag
from numpy import dot
from numpy import zeros
from scipy.linalg import svd
# define a matrix
A = array([[1, 2], [3, 4], [5, 6]])
print(A)
# Singular-value decomposition
U, s, VT = svd(A)
# create m x n Sigma matrix
Sigma = zeros((A.shape[0], A.shape[1]))
# populate Sigma with n x n diagonal matrix
Sigma[:A.shape[1], :A.shape[1]] = diag(s)
# reconstruct matrix
B = U.dot(Sigma.dot(VT))
print(B)


import numpy as np

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

class LogisticRegression:
    def __init__(self, eta=0.1, epoch=1000, batch_size=1):
        """
        :param eta:         学习率
        :param epoch:       迭代次数
        :param batch_size:  batch size, 默认随机梯度下降, 如果为None则采用全部训练集
        """
        self.w = None
        self.b = None
        self.eta = eta
        self.epoch = epoch
        self.batch_size = batch_size

    def fit(self, X, y, detailed=False):
        """
        :param X:         输入矩阵, 2维numpy数组
        :param y:         输入标签, 1维numpy数组
        :param detailed:  输出迭代详细信息
        :return:
        """
        if X.shape[0] != y.shape[0]:
            print("输入格式错误")
            return

        if self.batch_size is None:
            self.batch_size = X.shape[0]

        self.w = np.ones(X.shape[1])
        self.b = np.ones(1)
        num_of_batch = X.shape[0] // self.batch_size
        if X.shape[0] % self.batch_size != 0:
            num_of_batch += 1

        for i in range(self.epoch):
            for j in range(num_of_batch):
                start = j * self.batch_size
                end = min((j + 1) * self.batch_size, X.shape[0])
                error = self.probability(X[start : end, :]) - y[start : end]
                error = error.reshape([-1, 1])
                delta_w = np.sum(error * X[start : end, :], axis = 0)
                delta_b = np.sum(error)
                self.w -= self.eta * delta_w
                self.b -= self.eta * delta_b
            if detailed:
                print("epoch:", i, " w:", self.w, " b:", self.b)

    def probability(self, x):
        return sigmoid(np.sum(self.w * x, axis=-1) + self.b)

    def predict(self, x):
        if self.probability(x) > 0.5:
            return 1
        else:
            return 0


from scipy.sparse.linalg import eigsh
from numpy.linalg import eigh
import numpy as np

import time
import json

def laplacian(A):
    return np.diag(A.sum(axis=0)) - A

def normalized_laplacian(A):
    L = laplacian(A)
    D = np.diag(1 / np.diagonal(L))
    return np.sqrt(D) @ L @ np.sqrt(D)

def first_k_eigenvectors_sparse(M, k, which='SM'):
    return eigsh(M, k, which=which)[1]

def first_k_eigenvectors(M, k):
    eig_val, eig_vec = eigh(M)
    return np.fliplr(eig_vec[:, :k])

def spectral_embeddings(matrix, k, measure_time=False, not_sparse=False):
    if not_sparse:
        sparse_length = 10000000000000000
    else:
        start_sparse = time.time()
        try:
            eigv_sparse = first_k_eigenvectors_sparse(matrix, k)
            end_sparse = time.time()
        except Exception as e:
            end_sparse = time.time() + 10000000000000000
            print(e)
        sparse_length = end_sparse - start_sparse

    matrix_dense = matrix.todense()
    start_full = time.time()
    eigv_full = first_k_eigenvectors(matrix_dense, k)
    end_full = time.time()
    full_length = end_full - start_full

    if measure_time:
        return (eigv_sparse, sparse_length, 'Sparse') if sparse_length < full_length else (eigv_full, full_length, 'Full')
    else:
        return eigv_sparse if sparse_length < full_length else eigv_full

def save_json(obj, path):
    with open(path, 'w') as _file:
        json.dump(obj, _file)

def load_json(path):
    with open(path) as _file:
        return json.load(_file)