from __future__ import print_function
import numpy as np

def uniqueStates(states,rates):
    """
    Returns unique states and sums up the corresponding rates.
    States should be a 2d numpy array with on each row a state, and rates a 1d numpy array with length equal to the number of rows in states.

    This may be helpful in the transition function for summing up the rates of different transitions that lead to the same state
    """
    order     = np.lexsort(states.T)
    states    = states[order]
    diff      = np.ones(len(states), 'bool')
    diff[1:]  = (states[1:] != states[:-1]).any(-1)
    sums      = np.bincount(diff.cumsum() - 1, rates[order])
    return states[diff], sums

def number_of_partitions(max_range, max_sum):
    '''
    Returns an array arr of the same shape as max_range, where
    arr[j] = number of admissible partitions for
             j summands bounded by max_range[j:] and with sum <= max_sum
    '''
    M = max_sum + 1
    N = len(max_range)
    arr = np.zeros(shape=(M,N), dtype = int)
    arr[:,-1] = np.where(np.arange(M) <= min(max_range[-1], max_sum), 1, 0)
    for i in range(N-2,-1,-1):
        for j in range(max_range[i]+1):
            arr[j:,i] += arr[:M-j,i+1]
    return arr.sum(axis = 0)

def partition_zero(max_range, max_sum, out = None, n_part = None):
    '''
    Function that can be helpful for obtaining the state space of a discrete Markov chain or Markov decision processes.
    Returns a 2d-array with on the rows all possible partitions of the ranges `0,...,max_range[j]` that add up to at most `max_sum`.

    Code due to ptrj, see http://stackoverflow.com/a/36563744/1479342.

    Parameters
    ----------
    max_range : array or list of ints
        Gives the ranges for each element in the output array. Element `j` has range `np.arange(max_range[j]+1)`.
    max_sum : int
        The maximum sum for each partition in the output array.

    Returns
    -------
    out : array
        2d array with all possible partitions of the ranges `0,...,max_range[j]` summing up to at most `max_sum`.

    Example
    -------
    >>> max_range=np.array([1,3,2])
    >>> max_sum = 3
    >>> partition_zero(max_range,max_sum)
    array([[0, 0, 0],
           [0, 0, 1],
           [0, 0, 2],
           [0, 1, 0],
           [0, 1, 1],
           [0, 1, 2],
           [0, 2, 0],
           [0, 2, 1],
           [0, 3, 0],
           [1, 0, 0],
           [1, 0, 1],
           [1, 0, 2],
           [1, 1, 0],
           [1, 1, 1],
           [1, 2, 0]])
    '''
    if out is None:
        max_range = np.asarray(max_range, dtype = int).ravel()
        n_part = number_of_partitions(max_range, max_sum)
        out = np.zeros(shape = (n_part[0], max_range.size), dtype = int)

    if(max_range.size == 1):
        out[:] = np.arange(min(max_range[0],max_sum) + 1, dtype = int).reshape(-1,1)
        return out

    P = partition_zero(max_range[1:], max_sum, out=out[:n_part[1],1:], n_part = n_part[1:])

    S = np.minimum(max_sum - P.sum(axis = 1), max_range[0])
    offset, sz  = 0, S.size
    out[:sz,0] = 0
    for i in range(1, max_range[0]+1):
        ind, = np.nonzero(S)
        offset, sz = offset + sz, ind.size
        out[offset:offset+sz, 0] = i
        out[offset:offset+sz, 1:] = P[ind]
        S[ind] -= 1
    return out

def partition(min_range,max_range,max_sum=None):
    '''
    Function that can be helpful for obtaining the state space of a discrete Markov chain or Markov decision processes.
    Returns a 2d-array with on the rows all possible partitions of the ranges `min_range[j],...,max_range[j]` for each j.
    If the argument `max_sum` is specified, the numbers will add up to at most `max_sum`

    Parameters
    ----------
    min_range : array or list of ints
        `min_range[j]` gives the minimum value for element `j` in the output array.
    max_range : array or list of ints
        `max_range[j]` gives the maximum value for element `j` in the output array.
    max_sum : int
        Optional argument. The maximum sum for each partition in the output array.

    Returns
    -------
    out : array
        2d array with all possible partitions of the ranges `min_range[j],...,max_range[j]` summing up to at most `max_sum`.

    Example
    -------
    >>> min_range=np.array([1,2,1])
    >>> max_range=np.array([2,4,3])
    >>> max_sum = 6
    >>> partition(min_range,max_range,max_sum)
    array([[1, 2, 1],
           [1, 2, 2],
           [1, 2, 3],
           [1, 3, 1],
           [1, 3, 2],
           [1, 4, 1],
           [2, 2, 1],
           [2, 2, 2],
           [2, 3, 1]])
    '''
    max_range = np.asarray(max_range, dtype = int).ravel()
    min_range = np.asarray(min_range, dtype = int).ravel()
    full_range = max_range-min_range
    if any(full_range<0):
        raise ValueError("max_range needs to be larger than min_range")
    if max_sum == None:
        max_sum = np.sum(full_range)
    else:
        max_sum -= np.sum(min_range)
    out = partition_zero(full_range,max_sum)
    out += min_range
    return out


'''
Based on the video: https://www.youtube.com/watch?v=GiCJS9sUQts
'''
import matplotlib.pyplot as plt
import numpy as np
import numpy.polynomial.polynomial as poly

def plot_graphs():
    # fine
    x_start = -2.0
    x_stop = 2.1
    increment = 0.1
    x = np.arange(start=x_start, stop=x_stop, step=increment)
    y = x**2
    plt.plot(x, y)

    # course
    x_start = -2
    x_stop = 3
    increment = 1
    x = np.arange(start=x_start, stop=x_stop, step=increment)
    y = x**2
    plt.plot(x, y, '-o')

    plt.title('y(x)')
    plt.show()

def plot_solution():
    # course
    x_start = -2
    x_stop = 2.1
    increment = 0.1
    x = np.arange(start=x_start, stop=x_stop, step=increment)
    y = x**2

    # exact/analytical solution
    dydx = 2*x
    print(f'dydx={dydx}')
    plt.plot(x, dydx, 'o-')

    # numeric solution
    dydx_num = np.diff(y) / np.diff(x)
    print(f'dydx_num={dydx_num}')
    x_start = -2
    x_stop = 2
    x = np.arange(start=x_start, stop=x_stop, step=increment)
    plt.plot(x, dydx_num, 'o-')

    plt.title('dy/dx')
    plt.legend(['Exact', 'Numeric'])
    plt.show()

def polynomial_differentiation():
    # p(x) = x^3 + 2x^2 - x + 3
    # p(x) = 3 - x + 2x^2 + x^3
    p = [3, -1, 2, 1]
    dpdx = poly.polyder(p)
    print(f'dpdx = {dpdx}')

    # p(x) = x^3 + 2
    # p(x) = 2 + x^3
    # p(x) = 2 + 0x + 0x^2 + x^3
    p = [2, 0, 0, 1]
    dpdx = poly.polyder(p)
    print(f'dpdx = {dpdx}')

def main():
    # plot_graphs()
    # plot_solution()
    polynomial_differentiation()

if __name__ == "__main__":
    main()


import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

def european_opt_payoff(typ,S, K):
#typ ="type" when "c"=call, "p=put"
    if (typ=='c'):
        payoff=(abs(S-K)+(S-K))/2
    else:
        payoff=(abs(K-S)+(K-S))/2

    return payoff

def bullspread_payoff(S,K1,K2):
    typ='c'
    payoff = european_opt_payoff(typ,S, K1)-european_opt_payoff(typ,S, K2)
    return payoff

def bearspread_payoff(S,K1,K2):
    typ='p'
    payoff = european_opt_payoff(typ,S, K2)-european_opt_payoff(typ,S, K1)
    return payoff

def straddle_payoff(S,K):
    payoff = european_opt_payoff('c',S, K)+european_opt_payoff('p',S, K)
    return payoff

def strangle_payoff(S,K1,K2):
    payoff = european_opt_payoff('c',S, K2)+european_opt_payoff('p',S, K1)
    return payoff

def riskreversal_payoff(S,K1,K2):
    payoff = european_opt_payoff('c',S, K2)-european_opt_payoff('p',S, K1)
    return payoff

def butterfly_payoff(S,K,E):
    payoff = european_opt_payoff('c',S, K-E)-2*european_opt_payoff('c',S, K)+ \
    european_opt_payoff('c',S, K+E)
    return payoff

def condor_payoff(S,K1,K2,E):
    payoff = european_opt_payoff('c',S, K1-E)- \
    european_opt_payoff('c',S, K1)- \
    european_opt_payoff('c',S, K2)+ \
    european_opt_payoff('c',S, K2+E)
    return payoff

cc=8
rr=2

S=np.arange(1,15,0.01) #underlying asset prices
K1=3.0 #strike price
K2 =12.0
typ='c'
pay_off = bullspread_payoff(S, K1, K2)

plt.figure(figsize=(10,30))
plt.subplot(cc,rr,1)
#plt.axis([xmin,xmax,ymin,ymax])
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Bull Spread')
plt.grid(True)

S=np.arange(0,15,0.01) #underlying asset prices
K1=3 #strike price
K2=12 #strike price
pay_off = bearspread_payoff(S,K1,K2);

plt.subplot(cc,rr,2)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Bear Spread')
plt.grid(True)

S=np.arange(0,10,0.01) #underlying asset prices
K =5 #strike price

pay_off = straddle_payoff(S,K)

plt.subplot(cc,rr,3)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Stradlle')
plt.grid(True)

plt.subplot(cc,rr,4)
plt.axhline(0, color='black', lw=2)
plt.plot(S,-pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Short a Stradlle')
plt.grid(True)

S=np.arange(0,10,0.01) #underlying asset prices
K1 = 8.0 #strike price
K2 = 5.0 #strike price
pay_off = strangle_payoff(S,K1,K2)
plt.subplot(cc,rr,5)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('In-the-Money Strangle Option')
plt.grid(True)

S=np.arange(0,10,0.01) #underlying asset prices
K1 = 5.0 #strike price
K2 = 8.0 #strike price
pay_off = strangle_payoff(S,K1,K2)
plt.subplot(cc,rr,6)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Out-of-Money Strangle Option')
plt.grid(True)

S=np.arange(0,10,0.01) #underlying asset prices
K1 = 3.0 #strike price
K2 = 8.0 #strike price
if K1>=K2:
    print('Sorry, it is improper setting')
else:
    pay_off = riskreversal_payoff(S, K1, K2)

plt.subplot(cc,rr,7)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Risk Reversal Option')
plt.grid(True)

plt.subplot(cc,rr,8)
plt.axhline(0, color='black', lw=2)
plt.plot(S,-pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Short a Risk Reversal Option')
plt.grid(True)

S=np.arange(5,15,0.01) #underlying asset prices
K = 10.0 #strike price
E = 4.0 #strike price
if E>K:
    print('Sorry, it is improper setting')
else:
    pay_off = butterfly_payoff(S, K, E)

plt.subplot(cc,rr,9)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Butterfly Option')
plt.grid(True)

S=np.arange(0,15,0.01) #underlying asset prices
K1 = 6.0 #strike price
K2 = 10.0
E = 4.0 #strike price
if ((E>K)or(K1>K2)or(E>K2)):
    print('Sorry, it is improper setting')
else:
    pay_off = condor_payoff(S, K1, K2, E)

plt.subplot(cc,rr,10)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Condor Option')
plt.grid(True)


# Copyright (C) 2018 by Joon Jung
#
# This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
#

# Strassen Multiplication

# M1 = (A11 + A22)(B11 + B22)
# M2 = (A21 + A22)B11
# M3 = A11(B12 - B22)
# M4 = A22(B21 - B11)
# M5 = (A11 + A12)B22
# M6 = (A21 - A11)(B11 + B12)
# M7 = (A12 - A22)(B21 + B22)

# C11 = M1 + M4 - M5 + M7
# C12 = M3 + M5
# C21 = M2 + M4
# C22 = M1 - M2 + M3 + M6

import numpy as np

# INPUT: This implementation only allows square
# even numbered matrix. The user of
# this function is expected to pad or
# do something similar before feeding
# the data.
# OUTPUT: Multiplied Matrix

# Big O bound
# Each call of strassen_multiplication is composed of
# 7f(n-1) + constant operations. Therefore the bound is
# O((7 + O(1))^n and n = log(2, N).
# This leads to O(pow(N, log(2, 7)+O(1))) and gives
# roughly O(pow(N, 2.8)) bound.
def strassen_multiplication(a, b, n):

  n1, n2 = a.shape
  if n != n1 or n != n2:
    print("Only square matrices are acceptted. Please pad your matrices.")
    return 0
  elif n%2 != 0:
    print("Only even matrices are acceptted. Please pad your matrices.")
    return 0

  if n == 2:
    m1 = (a[0][0] + a[1][1]) * (b[0][0] + b[1][1])
    m2 = (a[1][0] + a[1][1]) * b[0][0]
    m3 = a[0][0] * (b[0][1] - b[1][1])
    m4 = a[1][1] * (b[1][0] - b[0][0])
    m5 = (a[0][0] + a[0][1]) * b[1][1]
    m6 = (a[1][0] - a[0][0]) * (b[0][0] + b[0][1])
    m7 = (a[0][1] - a[1][1]) * (b[1][0] + b[1][1])

    c11 = m1 + m4 - m5 + m7
    c12 = m3 + m5
    c21 = m2 + m4
    c22 = m1 - m2 + m3 + m6

    a = np.stack((c11, c21), axis=0)
    b = np.stack((c12, c22), axis=0)
    c = np.stack((a, b), axis=1)

    return c

  a11 = a[0:n//2, 0:n//2]
  a12 = a[0:n//2, n//2:n]
  a21 = a[n//2:n, 0:n//2]
  a22 = a[n//2:n, n//2:n]

  b11 = b[0:n//2, 0:n//2]
  b12 = b[0:n//2, n//2:n]
  b21 = b[n//2:n, 0:n//2]
  b22 = b[n//2:n, n//2:n]

  m1 = strassen_multiplication(a11 + a22, b11 + b22, n//2)
  m2 = strassen_multiplication(a21 + a22, b11, n//2)
  m3 = strassen_multiplication(a11, b12 - b22, n//2)
  m4 = strassen_multiplication(a22, b21 - b11, n//2)
  m5 = strassen_multiplication(a11 + a12, b22, n//2)
  m6 = strassen_multiplication(a21 - a11, b11 + b12, n//2)
  m7 = strassen_multiplication(a12 - a22, b21 + b22, n//2)

  c11 = m1 + m4 - m5 + m7
  c12 = m3 + m5
  c21 = m2 + m4
  c22 = m1 - m2 + m3 + m6

  a = np.concatenate((c11, c12), axis=1)
  b = np.concatenate((c21, c22), axis=1)
  c = np.concatenate((a, b), axis=0)

  return c

n = 8

a = np.zeros((n, n))
k = 0
for i in range(n):
  for j in range(n):
    a[i, j] = k
    k += 1

b = np.zeros((n, n))
for i in range(n):
  for j in range(n):
    b[i, j] = k
    k += 1

c = a.dot(b)
print(a)
print(b)
print(c)

c = strassen_multiplication(a, b, n)
print("Result:")
print(c)


from sympy import *
from rodrigues_R_utils import *

x_1, y_1, z_1 = symbols('x_1 y_1 z_1')
px_1, py_1, pz_1 = symbols('px_1 py_1 pz_1')
sx_1, sy_1, sz_1 = symbols('sx_1 sy_1 sz_1')
x_2, y_2, z_2 = symbols('x_2 y_2 z_2')
px_2, py_2, pz_2 = symbols('px_2 py_2 pz_2')
sx_2, sy_2, sz_2 = symbols('sx_2 sy_2 sz_2')
scale = symbols('scale')
all_symbols = [scale]

point_1 = Matrix([x_1, y_1, z_1, 1]).vec()
point_2 = Matrix([x_2, y_2, z_2, 1]).vec()

transformed_point_1 = (matrix44FromRodrigues(scale * px_1, scale * py_1, scale * pz_1, sx_1, sy_1, sz_1) * point_1)[:-1,:]
transformed_point_2 = (matrix44FromRodrigues(scale * px_2, scale * py_2, scale * pz_2, sx_2, sy_2, sz_2) * point_2)[:-1,:]

delta=Matrix([0,0,0]).vec()-(transformed_point_1-transformed_point_2)
delta_jacobian=delta.jacobian(all_symbols)

print(delta)
print(delta_jacobian)

with open("point_to_point_vo_scale_rodrigues_wc_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void point_to_point_vo_scale_rodrigues_wc(double &delta_x, double &delta_y, double &delta_z, double px_1, double py_1, double pz_1, double sx_1, double sy_1, double sz_1, double px_2, double py_2, double pz_2, double sx_2, double sy_2, double sz_2, double x_1, double y_1, double z_1, double x_2, double y_2, double z_2, double scale)\n")
    f_cpp.write("{")
    f_cpp.write("delta_x = %s;\n"%(ccode(delta[0,0])))
    f_cpp.write("delta_y = %s;\n"%(ccode(delta[1,0])))
    f_cpp.write("delta_z = %s;\n"%(ccode(delta[2,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void point_to_point_vo_scale_rodrigues_wc_jacobian(Eigen::Matrix<double, 3, 1> &j, double px_1, double py_1, double pz_1, double px_2, double py_2, double pz_2)\n")
    f_cpp.write("{")
    for i in range (3):
        for j in range (1):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(delta_jacobian[i,j])))
    f_cpp.write("}")


# exercise 2.2.2

from matplotlib.pyplot import (figure, subplot, plot, xlabel, ylabel, title,
yticks, show,legend,imshow, cm)
from scipy.io import loadmat
import scipy.linalg as linalg
import numpy as np

# Digits to include in analysis (to include all, n = range(10) )
n = [0,1]
# Number of principal components for reconstruction
K = 16
# Digits to visualize
nD = range(6);

# Load Matlab data file to python dict structure
# and extract variables of interest
traindata = loadmat('../Data/zipdata.mat')['traindata']
X = traindata[:,1:]
y = traindata[:,0]

N,M = X.shape
C = len(n)

classValues = n
classNames = [str(num) for num in n]
classDict = dict(zip(classNames,classValues))

# Select subset of digits classes to be inspected
class_mask = np.zeros(N).astype(bool)
for v in n:
    cmsk = (y == v)
    class_mask = class_mask | cmsk
X = X[class_mask,:]
y = y[class_mask]
N=X.shape[0]

# Center the data (subtract mean column values)
Xc = X - np.ones((N,1))*X.mean(0)

# PCA by computing SVD of Y
U,S,V = linalg.svd(Xc,full_matrices=False)
#U = mat(U)
V = V.T

# Compute variance explained by principal components
rho = (S*S) / (S*S).sum()

# Project data onto principal component space
Z = Xc @ V

# Plot variance explained
figure()
plot(rho,'o-')
title('Variance explained by principal components');
xlabel('Principal component');
ylabel('Variance explained value');

# Plot PCA of the data
f = figure()
title('pixel vectors of handwr. digits projected on PCs')
for c in n:
    # select indices belonging to class c:
    class_mask = (y == c)
    plot(Z[class_mask,0], Z[class_mask,1], 'o')
legend(classNames)
xlabel('PC1')
ylabel('PC2')

# Visualize the reconstructed data from the first K principal components
# Select randomly D digits.
figure(figsize=(10,3))
W = Z[:,range(K)] @ V[:,range(K)].T
D = len(nD)
for d in range(D):
    digit_ix = np.random.randint(0,N)
    subplot(2, D, d+1)
    I = np.reshape(X[digit_ix,:], (16,16))
    imshow(I, cmap=cm.gray_r)
    title('Original')
    subplot(2, D, D+d+1)
    I = np.reshape(W[digit_ix,:]+X.mean(0), (16,16))
    imshow(I, cmap=cm.gray_r)
    title('Reconstr.');

# Visualize the pricipal components
figure(figsize=(8,6))
for k in range(K):
    N1 = np.ceil(np.sqrt(K)); N2 = np.ceil(K/N1)
    subplot(N2, N1, k+1)
    I = np.reshape(V[:,k], (16,16))
    imshow(I, cmap=cm.hot)
    title('PC{0}'.format(k+1));

# output to screen
show()

print('Ran Exercise 2.2.2')


import numpy as np
import scipy.stats as stats

# ================================================================
# 95% CONFIDENCE INTERVAL FUNCTION
# ================================================================

def get_95_ci(x1, x2):
    #  Calculate a 95% CI for 2 1d numpy arrays
    signal = x1.mean() - x2.mean()
    noise = np.sqrt(x1.var() / x1.size + x2.var() / x2.size)
    ci_lo = signal - 1.96 * noise
    ci_hi = signal + 1.96 * noise
    return ci_lo, ci_hi

# ================================================================
# COMMON LANGUAGE EFFECT SIZE
# ================================================================

def cles_ind(x1, x2):
    """
    COMMON LANGUAGE EFFECT SIZE

    Interpret as the probability that a score sampled
    at random from one distribution will be greater than
    a score sampled from some other distribution.
    Based on: http://psycnet.apa.org/doi/10.1037/0033-2909.111.2.361

    :param x1: sample 1
    :param x2: sample 2
    :return: (float) common language effect size
    """
    x1 = np.array(x1)
    x2 = np.array(x2)
    diff = x1[:, None] - x2
    cles = max((diff < 0).sum(), (diff > 0).sum()) / diff.size
    return cles

# ================================================================
# 2 MEDIAN GROUP TEST CONFIDENCE INTERVAL
# ================================================================

def calc_non_param_ci(x1, x2, alpha=0.05):
    """Calc confidence interval for 2 group median test
    Process:
    * Find all pairwise diffs
    * Sort diffs
    * Find appropriate value of k
    * Choose lower bound from diffs as: diffs[k]
    * Choose upper bound from diffs as: diffs[-k]
    Based on: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2545906/

    :param x1: sample 1
    :param x2: sample 2
    :param alpha: significance level
    :return: (tuple) confidence interval bounds
    """

    x1 = np.array(x1)
    x2 = np.array(x2)

    n1 = x1.size
    n2 = x2.size
    cv = stats.norm.ppf(1 - alpha / 2)

    # Find pairwise differences for every datapoint in each group
    diffs = (x1[:, None] - x2).flatten()
    diffs.sort()

    # For an approximate (1-a)% confidence interval first calculate K:
    k = int(round(n1 * n2 / 2 - (cv * (n1 * n2 * (n1 + n2 + 1) / 12) ** 0.5)))

    # The Kth smallest to the Kth largest of the n x m differences
    # n1 and n2 should be > ~20
    ci_lo = diffs[k]
    ci_hi = diffs[-k]

    return ci_lo, ci_hi


import numpy as np
from random import shuffle

def sigmoid(x):
    """Sigmoid function implementation"""
    h = np.zeros_like(x)

    h=1/(1+np.exp(-x))
    return h

    #############################################################################
    # TODO: Implement sigmoid function.                                         #
    #############################################################################
    #############################################################################
    #                          START OF YOUR CODE                               #
    #############################################################################

    #############################################################################
    #                          END OF YOUR CODE                                 #
    #############################################################################

def logistic_regression_loss_naive(W, X, y, reg):
    """
      Logistic regression loss function, naive implementation (with loops)
      Use this linear classification method to find optimal decision boundary.

      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 c can be either 0 or 1.
      - reg: (float) regularization strength. For regularization, we use L2 norm.

      Returns a tuple of:
      - loss: (float) the mean value of loss functions over N examples in minibatch.
      - gradient: gradient wrt W, an array of same shape as W
    """
    # Set the loss to a random number
    loss = 0
    # Initialize the gradient to zero
    dW = np.zeros_like(W)
    num_train ,dimension = X.shape

    for i in range(num_train):
        xi = X[i,:]
        f_x = 0
        for j in range(xi.shape[0]):
            f_x += xi[j] * W[j]
        predict = sigmoid(f_x)
        loss += -(y[i] * np.log(predict) + (1 - y[i]) * np.log(1 - predict))
        b=np.array([[i] for i in xi])
        dW += -(int(y[i])-int(predict)) * b
    loss /= num_train
    loss += 0.5 * reg * np.sum(W * W) # add regularization

    dW =dW/num_train
    dW += reg * W # add regularization

    return loss, dW

def logistic_regression_loss_vectorized(W, X, y, reg):
    """
    Logistic regression loss function, vectorized version.
    Use this linear classification method to find optimal decision boundary.

    Inputs and outputs are the same as softmax_loss_naive.
    """
    # Set the loss to a random number
    loss = 0
    # Initialize the gradient to zero
    dW = np.zeros_like(W)
    num_train ,dimension = X.shape
    f_x_mat = X.dot(W)
    predict = 1.0 / (1.0 + np.exp(-f_x_mat)) # [N, 1]
    loss = -np.sum( np.log(predict)*y  +  np.log(1 - predict)*(1 - y) )
    loss = 1.0/num_train * loss + 0.5 * reg * np.sum(W * W)
    dW = (X.T).dot(predict - y) # [D, 1]
    dW = 1.0 / num_train * dW + reg * W

    return loss, dW


import numpy as np

# Basic array definition, and shortcuts for creating common arrays

array_1d = np.array([1, 2, 3])
array_2d = np.array([[1, 4, 3], [1, 5, 9]])   # 2 x 3 array
zeros = np.zeros((3, 4))
identity = np.identity(4)
rand = np.random.uniform(0, 1, size=(100, 3))
points = np.linspace(1, 2, 100)

# Basic info
array_1d.shape          # (3,)
array_2d.shape          # (3, 2)
array_2d.size           # 6

# Element access/assignment
array = np.array([[1,-1], [2, -2], [3, -3]])    # 3 x 2 array
array[0]        # [1, -1]               - Row access
array[:,1]      # [-1, -2, 3]           - Column access
array[:,0:1]    # [[-1], [-2], [-3]]    - Slicing notation - Note that you still have a 2D array of shape 3x1
array[0,0]      # 1
array[0,0] = 5                          # Single element assignment
array[1:,0:] = np.identity(2)           # Multiple element assignment - Must have consistent shape
                                        # Final array: [[5, -1], [1, 0], [0, 1]]

# Resizing array
# None of these operations are in-place, so you must assign to a new variable to have an effect!
mat_1 = np.random.uniform(size=(6, 4))      # 6x2 array
mat_1.T                                     # Transpose, 4x6 array
np.reshape(mat_1, (24,))                    # 24-vector
np.reshape(mat_1, (-1,))                    # -1 means "infer this dimension based off the others" - Also a 24-vector
np.reshape(mat_1, (4, -1))                  # Inferred dimension is 24/4 = 6, so get 4x6 array
np.reshape(mat_1, (-1, 2, 3))               # Inferred dimension is 24/(2*3) = 4, so get 4x2x3 array

# Basic arithmetic
array_a = np.array([1, 2, -3.5])
array_b = np.array([3, 1, 2])

array_a + array_b   # [4, 3, -1.5]
array_a * array_b   # [3, 2, -7.0]
array_a - 3         # [-2, -1, -6.5]
2 ** array_b        # [8, 2, 4]

# Arithmetic operations involving an NxD array and a D-vector
# Will do "row-wise" operations to the NxD array
array_2d - array_1d     # [[0, 2, 0], [0, 3, 6]]

# Matrix operations
mat_1 = np.random.uniform(size=(3, 2))
mat_2 = np.random.uniform(size=(2, 2))
np.linalg.inv(mat_2)    # Inverse
mat_1 @ mat_2           # Matrix multiplication - equivalent to mat_1.dot(mat_2)

# Truth operations and truth-based subsetting
truth_index = array_1d > 1.5        # Boolean array of [False, True, True]
array_1d[truth_index]               # [2, 3]
np.all(truth_index)                 # False, because not all of the values in the array are True
np.any(truth_index)                 # True, because at least one of the values in the array is True

# Index-based subsetting
vals = np.array([[2, 3], [4, 5], [1, 1]])   # 3x2 array
to_index = np.array([0, 2])
vals[to_index]                              # [[2, 3], [1, 1]]
sorting_index = np.array([1, 0, 2])
vals[sorting_index]                         # [[4, 5], [2, 3], [1, 1]]

# Summary stats
stats = np.array([[1, 2, 3], [-1, 5, 4], [3, 4, 1], [9, 8, 3]])     # 4x3 array
stats.mean()                    # 3.5                           - Mean over all 12 elements
stats.mean(axis=0)              # [3, 4.75, 2.75]               - Condenses column-wise to get a 3-vector
stats.mean(axis=1)              # [2, 2.667, 2.667, 6.667]      - Condenses row-wise to get a 4-vector
np.linalg.norm(stats, axis=1)   # [3.74, 6.48, 5.10, 12.41]     - Point norms


import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import expon
from scipy.stats import norm

# Parte 2. Genere 1000 numeros aleatorias con una distribucion exponencial, grafique el histograma y compare con la PDF conocida de dicha distribucion.

# Luego Realice 1000 sumas de 1000 numeros aleatorios con una distribucion exponencial y compare (haga un fit) a una distribucion normal, verificando el teorema del limite central.

n=[]
for i in range(1000):
    n.append(np.random.exponential(10)) # Llenamos la lista "n" con numeros aleatorios con distribución exponencial de media 10.

loc1,scale1 = expon.fit(n)  # obetenemos los parámetros "Scale" y "loc" de un fit sobre los datos en la lista "n". Para este caso, la media de la distribución es igual a "scale".
print(scale1,loc1) #imrpimimos estos parámetros

x = np.linspace(0,50, 100)
y=expon.pdf(x,scale=scale1, loc=loc1)  # Graficamos una distribución exponencial con media 10.

f, fig1 = plt.subplots(1,1)

fig1.plot(x, y,'r-', lw=5, alpha=0.6, label='expon pdf') #Graficamos x vs y (distribución)
fig1.hist(n,bins=50,normed=True) #Hacemos el histograma de n. Es importante que esté normalizado.
f.savefig('graficas.png') #Guardamos en una archivo las gráficas.

#Hasta aca verificamos que los datos si pertenecen a la distribución dada. Ahora tenemos que repetir el proceso creando una variable que es la suma de las variables generadas.

sumas=[] #En cada elemento de la lista "sumas" guardamos la suma de 1000 varables aleatorias con distribución exponencial.
for i in range(1000):
    suma=0
    for j in range(1000):
        suma+=np.random.exponential(10)
    sumas.append(suma)

loc2,scale2 = norm.fit(sumas)   # obetenemos los parámetros "scale" y "loc" de un fit sobre los datos en la lista "n". Para este casp, la media de la distribuciíon es igual a "loc" y la desviación estandar a "scale".
print(scale2,loc2)

x2 = np.linspace(8000,11000,10000)
y2=norm.pdf(x2,loc=loc2,scale=scale2)  # Graficamos una distribución normal con media igual a "loc2" y desviación estandar igual a "scale".

f2, fig2 = plt.subplots(1,1)
fig2.plot(x2, y2,'r-', lw=5, alpha=0.6, label='norm pdf')
fig2.hist(sumas,bins=50, normed=True)
f2.savefig('limite.png')


"""Tool for solving systems of linear equations
using Gaussian elimination algorithm

Functions:
    solve_system(A, b, is_modified=False) -> ndarray
    forward_elimination(matrix) -> None
    forward_elimination_m(matrix) -> None
    back_substitution(matrix) -> ndarray
    print_log() -> None
"""

__all__ = [
    'solve_system', 'forward_elimination_m', 'forward_elimination',
    'back_substitution', 'print_log'
]

import numpy as np

def solve_system(A, b, is_modified=False):
    """Solve system of linear equations A x = b and return x.

    Arguments:
    A -- square matrix
    b -- vector
    Keyword arguments:
    is_modified -- selection flag of forward elimination(default False)
    """

    log['A'] = A
    log['b'] = b
    log['forward elimination is modified'] = is_modified
    extd_matrix = np.hstack((A, np.array([b]).T))
    log['extended matrix'] = extd_matrix.copy()
    if is_modified:
        forward_elimination_m(extd_matrix)
    else:
        forward_elimination(extd_matrix)

    x = back_substitution(extd_matrix)
    log['triangular matrix'] = extd_matrix
    log['solution of system'] = x
    return x

def forward_elimination_m(matrix):
    """Perform the forward elimination over the given matrix.

    This function applies a forward elimination using the selection
    of the main elements

    Arguments:
    matrix -- two-dimensional matrix
    """

    ctrl_vector = matrix.sum(axis = 1)
    log['control vector before forward elimination'] = ctrl_vector.copy()
    n = matrix.shape[0]
    for k in range(n):
        # search for the main elements(max by column) and rows swapping
        p = k + abs(matrix[k:,k]).argmax()
        matrix[p], matrix[k] = matrix[k], matrix[p].copy()
        ctrl_vector[p], ctrl_vector[k] = ctrl_vector[k], ctrl_vector[p]

        ctrl_vector[k] /= matrix[k][k]
        matrix[k] /= matrix[k][k]
        for i in range(k + 1, n):
            ctrl_vector[i] -= ctrl_vector[k] * matrix[i][k]
            matrix[i] -= matrix[k] * matrix[i][k]

    log['control vector after forward elimination'] = ctrl_vector

def forward_elimination(matrix):
    """Perform the forward elimination over the given matrix.

    Arguments:
    matrix -- two-dimensional matrix
    """

    ctrl_vector = matrix.sum(axis = 1)
    log['control vector before forward elimination'] = ctrl_vector.copy()
    n = matrix.shape[0]
    for k in range(n):
        ctrl_vector[k] /= matrix[k][k]
        matrix[k] /= matrix[k][k]
        for i in range(k + 1, n):
            ctrl_vector[i] -= ctrl_vector[k] * matrix[i][k]
            matrix[i] -= matrix[k] * matrix[i][k]

    log['control vector after forward elimination'] = ctrl_vector

def back_substitution(matrix):
    """Perform back substitution and return solution of system

    Arguments:
    matrix -- two-dimensional matrix

    Attention: given argument must be a upper triangular matrix
    with ones on main diagonal and have n rows and n + 1 columns
    (last column is vector b)
    """

    n = matrix.shape[0]
    x = np.zeros(n, complex)
    for i in range(n - 1, -1, -1):
        x[i] = matrix[i, n] - sum(matrix[i, :n] * x)

    return np.real_if_close(x)

def print_log():
    """Print a log that stores step-by-step actions"""
    for key, value in log.items():
        print(key + ':\n', value, end='\n\n')

log = dict()


import numpy as np
from numpy import random as rd
from joblib import Parallel, delayed
from math import gamma, pi
import matplotlib.pyplot as plt
rd.seed(12163)
def vol_Aux(d):
    x = rd.uniform(-1,1,d)
    return (sum(x ** 2) < 1)

def vol(N,d):
    # Parallel prossesing for more speed!!!
    results = Parallel(n_jobs=-1, backend="multiprocessing")(
             map(delayed(vol_Aux), [d for i in range(N)]))
    nhits = sum(results)
    return (2 ** d * nhits / N)

def Vol1(d):
    x = d/2
    return pi ** x / gamma(x + 1)

d = 2
N = 1_000_000
MonteCarlo = []
Actual = []
Error = []
while True:
    current = vol(N,d)
    if current == 0: break
    actual = Vol1(d)
    MonteCarlo.append(current)
    Actual.append(actual)
    Error.append(abs(actual - current))
    d += 1

plt.subplot()
plt.plot(MonteCarlo, color = 'b', label = 'Monte Carlo Est')
plt.plot(Actual, color = 'g', label = 'Actual')
plt.plot(Error, color = 'r', label = 'Error')
plt.xlabel(r'Dimensions d')
plt.ylabel(r'V($Sphere$)')
plt.title(r'$p(d) = \frac{\omega (d)}{2^d}$')
plt.legend()
plt.grid()
plt.show()
print( "Monte Carlo: ", np.array(MonteCarlo) )
print("Actual: ", np.array(Actual))
print("Error: ", np.array(Error))


import numpy as np
from scipy.special import expit

sigmoid = expit

def generate_orthogonal_matrix(N):
    """
    with random permutation of coordinate axes
    :param N: dimension of matrix
    :return: NxN orthogonal matrix contains 0 and 1

    Ref: http://stackoverflow.com/questions/33003341/how-to-randomly-generate-a-nonnegative-orthogonal-matrix-in-numpy
    """
    I = np.eye(N)
    p = np.random.permutation(N)
    return I[p]

def solve_df_lambda(X: np.ndarray, dimension=None, epsilon=1e-3, do_standardization=True) -> list:
    """
    Using Newton Method to solve df(lambda) = k, k in [1..p]
    :param X:
    :return: list of lambda

    ref
    ----
    https://en.wikipedia.org/wiki/Newton%27s_method
    """
    if dimension is None:
        dimension = X.shape[1]

    if do_standardization:
        X  = (X-np.mean(X, axis=0)) / np.std(X, axis=0, ddof=1)

    u, d_, vt = np.linalg.svd(X)
    ds = d_**2

    func_d = lambda lam, k: np.sum(ds/(ds + lam)) - k
    func_d_derivative = lambda lam: -np.sum(ds/(ds + lam)**2)

    # init lambda_p is 0
    lambdas = [0] * (dimension + 1)

    for k in reversed(range(1, dimension)):
        last_lam = lambdas[k+1]
        while True:
            new_lambda = last_lam - func_d(last_lam, k) / func_d_derivative(last_lam)
            if abs((new_lambda - last_lam)/new_lambda) < epsilon:
                lambdas[k] = new_lambda
                break
            last_lam = new_lambda

    lambdas[0] = np.inf
    return lambdas


# -*- coding: utf-8 -*-
"""
Created on Sat Mar  9 11:05:33 2019

@author: Ham

HackerRanch Challenge: Array Mathematics

Basic mathematical functions operate element-wise on arrays.
They are available both as operator overloads and as functions in the NumPy module.

import numpy

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

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

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

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

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

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

print a**b                      #[  1.00000000e+00   6.40000000e+01 2.187e+3 6.5536e+4]
print numpy.power(a, b)         #[  1.00000000e+00   6.40000000e+01 2.187e+3 6.5536e+4]

Task

You are given two integer arrays, A and B of dimensions NxM.
Your task is to perform the following operations:

Add (A + B)
Subtract (A - B)
Multiply (A * B)
Integer Division (A / B)
Mod (A % B)
Power (A ** B)

Input Format

The first line contains two space separated integers, N and M.
The next N lines contains M space separated integers of array A.
The following N lines contains M space separated integers of array B.

Output Format

Print the result of each operation in the given order under Task.

Sample Input

1 4
1 2 3 4
5 6 7 8

Sample Output

[[ 6  8 10 12]]
[[-4 -4 -4 -4]]
[[ 5 12 21 32]]
[[0 0 0 0]]
[[1 2 3 4]]
[[    1    64  2187 65536]]

Use // for division in Python 3.

"""

import io
import numpy

STDIN_SIO = io.StringIO("""
1 4
1 2 3 4
5 6 7 8
""".strip())

if __name__ == '__main__':
    #numpy.set_printoptions(legacy='1.13')
    # set_printoptions() above came from a Discussion forum
    #n, m = map(int, input().strip().split())
    #a = numpy.array([input().strip().split() for _ in range(n)], int)
    #b = numpy.array([input().strip().split() for _ in range(n)], int)
    n, m = map(int, STDIN_SIO.readline().strip().split())
    a = numpy.array([STDIN_SIO.readline().strip().split()
                     for _ in range(n)], int)
    b = numpy.array([STDIN_SIO.readline().strip().split()
                     for _ in range(n)], int)
    #print(a)
    #print(b)
    print(numpy.add(a, b))
    print(numpy.subtract(a, b))
    print(numpy.multiply(a, b))
    print(a // b)
    print(numpy.mod(a, b))
    print(numpy.power(a, b))


import numpy as np
from numpy import arctan2, fliplr, flipud
import matplotlib.pyplot as plt
import cv2

def gradient(image, same_size=False):
    """ Computes the Gradients of the image separated pixel difference

    Gradient of X is computed using the filter
        [-1, 0, 1]
    Gradient of X is computed using the filter
        [[1,
          0,
          -1]]
    Parameters
    ----------
    image: image of shape (imy, imx)
    same_size: boolean, optional, default is True
        If True, boundaries are duplicated so that the gradients
        has the same size as the original image.
        Otherwise, the gradients will have shape (imy-2, imx-2)

    Returns
    -------
    (Gradient X, Gradient Y), two numpy array with the same shape as image
        (if same_size=True)
    """
    sy, sx = image.shape
    if same_size:
        gx = np.zeros(image.shape)
        gx[:, 1:-1] = -image[:, :-2] + image[:, 2:]
        gx[:, 0] = -image[:, 0] + image[:, 1]
        gx[:, -1] = -image[:, -2] + image[:, -1]

        gy = np.zeros(image.shape)
        gy[1:-1, :] = image[:-2, :] - image[2:, :]
        gy[0, :] = image[0, :] - image[1, :]
        gy[-1, :] = image[-2, :] - image[-1, :]

    else:
        gx = np.zeros((sy-2, sx-2))
        gx[:, :] = -image[1:-1, :-2] + image[1:-1, 2:]

        gy = np.zeros((sy-2, sx-2))
        gy[:, :] = image[:-2, 1:-1] - image[2:, 1:-1]

    return gx, gy

def magnitude_orientation(gx, gy):
    """ Computes the magnitude and orientation matrices from the gradients gx gy
    Parameters
    ----------
    gx: gradient following the x axis of the image
    gy: gradient following the y axis of the image

    Returns
    -------
    (magnitude, orientation)

    """

    magnitude = np.sqrt(gx**2 + gy**2)
    orientation = (arctan2(gy, gx) * 180 / np.pi) % 360

    return magnitude, orientation

if __name__ == '__main__':
    image = cv2.cvtColor(cv2.imread("Q_3.jpg"), cv2.COLOR_BGR2GRAY)

    gx, gy = gradient(image)
    # print('==================value gx========')
    # # print(gx)
    # plt.hist(gx)
    # plt.show()
    # print('==================value gy========')
    # # print(gy)
    # plt.title('')
    # plt.hist(gy)
    # plt.show()

    magnitude, orientation = magnitude_orientation(gx, gy)
    print('==================magnitude========')
    print(magnitude)
    plt.title('Magnitude')
    plt.hist(magnitude)
    #plt.imsave('magnitude.png', magnitude, format='png')
    plt.show()
    print('==================orienftation========')
    print(orientation)
    plt.title('orientation')
    plt.hist(orientation)
    #plt.imsave('orientation.png', orientation, format='png')
    plt.show()


# IMPORTS
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.style as style
from IPython.core.display import HTML

# PLOTTING CONFIG
%matplotlib inline
style.use('fivethirtyeight')
plt.rcParams["figure.figsize"] = (14, 7)
HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: center;
}
</style>
""")
plt.figure(dpi=100)

# PDF LAM = 1
plt.scatter(np.arange(20),
            (stats.poisson.pmf(np.arange(20), mu=1)),#/np.max(stats.poisson.pmf(np.arange(20), mu=1))),
            alpha=0.75,
            s=100
       )
plt.plot(np.arange(20),
         (stats.poisson.pmf(np.arange(20), mu=1)),#/np.max(stats.poisson.pmf(np.arange(20), mu=1))),
         alpha=0.75,
        )

# PDF LAM = 5
plt.scatter(np.arange(20),
            (stats.poisson.pmf(np.arange(20), mu=5)),
            alpha=0.75,
            s=100
       )
plt.plot(np.arange(20),
         (stats.poisson.pmf(np.arange(20), mu=5)),
         alpha=0.75,
        )

# PDF LAM = 10
plt.scatter(np.arange(20),
            (stats.poisson.pmf(np.arange(20), mu=10)),
            alpha=0.75,
            s=100
       )
plt.plot(np.arange(20),
         (stats.poisson.pmf(np.arange(20), mu=10)),
         alpha=0.75,
        )

# LEGEND
plt.text(x=3, y=.1, s="$\lambda = 1$", alpha=.75, rotation=-65, weight="bold", color="#008fd5")
plt.text(x=8.25, y=.075, s="$\lambda = 5$", alpha=.75, rotation=-35, weight="bold", color="#fc4f30")
plt.text(x=14.5, y=.06, s="$\lambda = 10$", alpha=.75, rotation=-20, weight="bold", color="#e5ae38")

# TICKS
plt.xticks(range(21)[::2])
plt.tick_params(axis = 'both', which = 'major', labelsize = 18)
plt.axhline(y = 0, color = 'black', linewidth = 1.3, alpha = .7)

# TITLE, SUBTITLE & FOOTER
plt.text(x = -2.5, y = .475, s = "Poisson Distribution - $\lambda$",
               fontsize = 26, weight = 'bold', alpha = .75)
plt.text(x = -2.5, y = .425,
         s = 'Depicted below are three Poisson distributed random variables with varying $\lambda $. As one can easily\nsee the parameter $\lambda$ shifts and flattens the distribution (the smaller $ \lambda $ the sharper the function).',
         fontsize = 19, alpha = .85)
plt.text(x = -2.5,y = -0.075,
         s = '   ©Joshua Görner                                                                                github.com/jgoerner   ',
         fontsize = 14, color = '#f0f0f0', backgroundcolor = 'grey');


# task II.10.6.п

import gauss
import seidel
import math_utils
import numpy as np

# set this to True if you want to see A, b, x
dump_matrices = False

def create_matrices(): # -> A, b
	n = 99
	A = np.zeros((n + 1, n + 1))

	for i in range(n):
		A[i][i] = 10 + (i + 1)
		A[i][i + 1] = 1
		A[i + 1][i] = 1
	for j in range(1, n + 1): # 1, 2, ..., n
		A[n][j] = 2
	A[n][0] = A[n][n] = 1

	b = np.fromfunction(lambda i: (i + 1) / n, (n + 1,))
	return A, b

A, b = create_matrices()
if dump_matrices:
	print("A =")
	print(A)
	print("B =", b)

print("condition number of A: ", math_utils.condition_number(A))
print("max eigen value of A: ", math_utils.maxabs_eigen_value(A))
print("min eigen value of A: ", math_utils.minabs_eigen_value(A))

x_gauss = gauss.solve_linear(A, b)
if dump_matrices:
	print("gauss: x =", x_gauss)
print("gauss: ||Ax - b|| =", math_utils.vec_norm_3(np.matmul(A, x_gauss) - b))

x_seidel = seidel.solve_linear(A, b, 1e-15)
if dump_matrices:
	print("seidel: x =", x_seidel)
print("seidel: ||Ax - b|| =", math_utils.vec_norm_3(np.matmul(A, x_seidel) - b))


import math as m
import matplotlib.pyplot as plt
import numpy as np
from sympy import *

epsilon = 0.01
x_min = -10
x_max = 60
x = np.linspace(x_min,x_max,10000)
theta_values = []

y_initial = 1.5
v_initial = 25
g = 9.81
x0 = 50
x02 = 0
theta_initial = m.radians(30)
y = m.tan(theta_initial)*x - g/(2*v_initial**2*(m.cos(theta_initial))**2)*x**2+y_initial

x1 = Symbol('x')
f = Function('f')(x)
f = m.tan(theta_initial)*x1 - (g/ (2 * v_initial**2 * m.cos(theta_initial)**2) * x1**2) + y_initial
f_prime = diff(f, x1)
f_prime2 = diff(f, x1, 2)

def y(x):
    return m.tan(theta_initial)*x - g/(2*v_initial**2*(m.cos(theta_initial))**2)*x**2+y_initial

def y_prime(x):
    return -0.659675690427106*x - 6.40533119664628

def y_prime_squared():
    return-0.659675690427106

def quadratic_approx(x):
    return y(x0)+y_prime(x0)*(x-x0)+0.5*y_prime_squared()*(x-x0)**2

y_approx_values = []
y_values = []

for i in x:
    y_approx_values.append(quadratic_approx(i))
    y_values.append(y(i))

# for i in x:
#     y_values.append(float(y))
plt.hlines(0,x_min,x_max)
# plt.plot(x,y_approx_values)
plt.plot(x,y_values)
plt.show()

for i in x:
    if -epsilon<=quadratic_approx(i)<=epsilon:
        print(float(quadratic_approx(i)),i)

def newton(f,Df,x0,epsilon,max_iter):
    '''Approximate solution of f(x)=0 by Newton's method.

    Parameters
    ----------
    f : function
        Function for which we are searching for a solution f(x)=0.
    Df : function
        Derivative of f(x).
    x0 : number
        Initial guess for a solution f(x)=0.
    epsilon : number
        Stopping criteria is abs(f(x)) < epsilon.
    max_iter : integer
        Maximum number of iterations of Newton's method.

    Returns
    -------
    xn : number
        Implement Newton's method: compute the linear approximation
        of f(x) at xn and find x intercept by the formula
            x = xn - f(xn)/Df(xn)
        Continue until abs(f(xn)) < epsilon and return xn.
        If Df(xn) == 0, return None. If the number of iterations
        exceeds max_iter, then return None.

    Examples
    --------
    >>> f = lambda x: x**2 - x - 1
    >>> Df = lambda x: 2*x - 1
    >>> newton(f,Df,1,1e-8,10)
    Found solution after 5 iterations.
    1.618033988749989
    '''
    xn = x0
    for n in range(0,max_iter):
        fxn = f(xn)
        if abs(fxn) < epsilon:
            # print('Found solution after',n,'iterations.')
            return xn
        Dfxn = Df(xn)
        if Dfxn == 0:
            # print('Zero derivative. No solution found.')
            return None
        xn = xn - fxn/Dfxn
    print('Exceeded maximum iterations. No solution found.')
    return None

f = lambda x: m.tan(theta_initial)*x - g/(2*v_initial**2*(m.cos(theta_initial))**2)*x**2+y_initial
df= lambda x: m.tan(theta_initial) - 2*g/(2*v_initial**2*(m.cos(theta_initial))**2)*x
dff = lambda : - 2*g/(2*v_initial**2*(m.cos(theta_initial))**2)

max_iter=10000
solution = newton(f,df,x0,epsilon,max_iter)
solution2 = newton(f,df,x02,epsilon,max_iter)

print(f'The first solution: {solution}')
print(f'The second solution: {solution2}')

"""Finding the maximum value of the graph"""
# set the expression, y, equal to 0 and solve
result1 = solve(f_prime,x1)
print(result1)

"""sensitivity part"""
theta_sensitivity = np.linspace(m.radians(30),m.radians(45),15)
new_x_values =  []
theta_values = []

while theta_initial <=m.radians(45):
    z = newton(f,df,x0,epsilon,max_iter)
    theta_values.append(theta_initial)
    theta_initial += m.radians(1)
    new_x_values.append(z)

n_theta_values = []
n_new_x_values = []
for element in theta_values:
    if element != None and element>0.06:
        n_theta_values.append(element)

for element in new_x_values:
    if element != None:
        n_new_x_values.append(element)
    # if element == None:
    #     n_new_x_values.append(0)

plt.plot(n_theta_values,n_new_x_values)
plt.show()


from sympy import *

print('----------------------------------------------------')
print('')
print('TORUS VOLUME CALCULATOR')
print('')
print('this python sheet is designed to compare different methods to calculate')
print('the volume of a rotated circle body, i.e. torus')
print('')

x = symbols('x') #define x as symbols

#basic informations (feel free to modify them)
circle_center_x = 0        #X-coordinate of the circle center
circle_center_y = 2        #X-coordinate of the circle center
circle_radius = 1          #radius of the circle (!!!!!! must be smaller than circle_center_y and positive)
number_of_divisions = 5    #number of divisions for the trapezoidal rule (the more the better approximation)

#function definition and bordersettings of the integral
f1 = circle_center_y + sqrt((circle_radius)**2 - (x-circle_center_x)**2)    #function 1 top part of circle
f2 = circle_center_y - sqrt((circle_radius)**2 - (x-circle_center_x)**2)    #function 2 bottom part of circle
integration_border_a = circle_center_x - circle_radius     #lower limit of the definite integral
integration_border_b = circle_center_x + circle_radius     #upper limit of the definite integral

def printVolumes():
    printExact() #without integral
    printNormal()
    printTrapeze()
    printSimpson()

def printExact():
    print('Exact volume without numeric integration')
    volume = calcExact()
    print('Volume as float: ' + str(float(volume)))
    print('Volume exact: ' + str(volume))
    print('')

def printNormal():
    print('Normal')
    volume = calcNormal()
    print('Volume as float: ' + str(float(volume)))
    print('Volume exact: ' + str(volume))
    print('Volumedifference from exact calculation: ' + str(float(calcExact() - volume)))
    print('')

def printTrapeze():
    print('Trapeze rule')
    volume = calcTrapeze(number_of_divisions)
    print('Volume as float: ' + str(float(volume)))
    print('Volume exact: ' + str(volume))
    print('Volumedifference from exact calculation: ' + str(float(calcExact() - volume)))
    print('Hint: Play arount with <<number_of_divisions>> variable value for better approximation')
    print('')

def printSimpson():
    print('Simpsons rule')
    volume = calcSimpson(number_of_divisions)
    print('Volume as float: ' + str(float(volume)))
    print('Volume exact: ' + str(volume))
    print('Volumedifference from exact calculation: ' + str(float(calcExact() - volume)))
    print('Hint: Play arount with <<number_of_divisions>> variable value for better approximation')
    print('')

def calcExact():
    volume = 2 * pi**2 * circle_center_y * circle_radius**2 #V = 2 * π² * R * r²
    return volume

def calcNormal():
    s1 = integrate(f1**2, (x, integration_border_a, integration_border_b)) #s1 is the surface of the integral from f1
    s2 = integrate(f2**2, (x, integration_border_a, integration_border_b)) #s2 is the surface of the integral from f2
    volume = pi * (s1 - s2) #volume of rotation body is defined as pi * surface of the body -> body is the circle so s1 - s2
    return volume

def calcTrapeze(n):
    s1 = trapezRule(f1**2, n) #s1 is the surface of the integral from f1 with trapezerule
    s2 = trapezRule(f2**2, n) #s2 is the surface of the integral from f2 with trapezerule
    volume = pi * (s1 - s2) #volume of rotation body is defined as pi * surface of the body -> body is the circle so s1 - s2
    return volume

def trapezRule(function, n):
    h = (integration_border_b - integration_border_a) / n   # h = (b-a)/n
    Sum = 0.5 * (function.subs(x, integration_border_a) + function.subs(x, integration_border_b))
    for i in range(1, n):
        Sum += function.subs(x, (integration_border_a + i*h)) #
    Integral = h * Sum
    return Integral

def calcSimpson(n):
    s1 = simpsonRule(f1**2, n) #s1 is the surface of the integral from f1 with simpsonrule
    s2 = simpsonRule(f2**2, n) #s2 is the surface of the integral from f2 with simpsonrule
    volume = pi * (s1 - s2) #volume of rotation body is defined as pi * surface of the body -> body is the circle so s1 - s2
    return volume

def simpsonRule(function, n):
    steper = (abs(integration_border_a) + abs(integration_border_b)) / n #partial interval size
    part_border_a = integration_border_a
    part_border_b = part_border_a + steper
    Sum = 0
    for i in range(n):
        h = (part_border_a + part_border_b) / 2 # h = (a+b)/2
        Sum += ((part_border_b - part_border_a)/6) * (function.subs(x, part_border_a) + (4*function.subs(x, h)) + function.subs(x, part_border_b))
        part_border_a = part_border_b
        part_border_b += steper

    return Sum

#start programm
printVolumes()


import superimport

import numpy as np
from numpy.linalg import cholesky
from matplotlib import pyplot as plt
from scipy.stats import multivariate_normal
from numpy.linalg import inv
import pyprobml_utils as pml

np.random.seed(10)

def gaussSample(mu, sigma, n):
    A = cholesky(sigma)
    Z = np.random.normal(loc=0, scale=1, size=(len(mu), n))
    return np.dot(A, Z).T + mu

mtrue = {}
prior = {}
muTrue = np.array([0.5, 0.5])
Ctrue = 0.1 * np.array([[2, 1], [1, 1]])
mtrue['mu'] = muTrue
mtrue['Sigma'] = Ctrue
xyrange = np.array([[-1, 1], [-1, 1]])
ns = [10]
X = gaussSample(mtrue['mu'], mtrue['Sigma'], ns[-1])

#fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(24, 8))
#fig.suptitle('gauss2dUpdateData')
fig, ax1 = plt.subplots()
ax1.plot(X[:, 0], X[:, 1], 'o', markersize=8, markerfacecolor='b')
ax1.set_ylim([-1, 1])
ax1.set_xlim([-1, 1])
ax1.set_title('data')
ax1.plot(muTrue[0], muTrue[1], 'x', linewidth=5, markersize=20, color='k')
pml.savefig("gauss_2d_update_data.pdf")

prior['mu'] = np.array([0, 0])
prior['Sigma'] = 0.1 * np.eye(2)

npoints = 100j
out = np.mgrid[xyrange[0, 0]:xyrange[0, 1]:npoints, xyrange[1, 0]:xyrange[1, 1]:npoints]
X1, X2 = out[0], out[1]
nr = X1.shape[0]
nc = X2.shape[0]
points = np.vstack([np.ravel(X1), np.ravel(X2)]).T
p = multivariate_normal.pdf(points, mean=prior['mu'], cov=prior['Sigma']).reshape(nr, nc)
fig, ax2 = plt.subplots()
ax2.contour(X1, X2, p)
ax2.set_ylim([-1, 1])
ax2.set_xlim([-1, 1])
ax2.set_title('prior')
pml.savefig("gauss_2d_update_prior.pdf")

post = {}
data = X[:ns[0], :]
n = ns[0]
S0 = prior['Sigma']
S0inv = inv(S0)
S = Ctrue
Sinv = inv(S)
Sn = inv(S0inv + n * Sinv)
mu0 = prior['mu']
xbar = np.mean(data, 0)
muN = np.dot(Sn, (np.dot(n, np.dot(Sinv, xbar)) + np.dot(S0inv, mu0)))

post['mu'] = muN
post['Sigma'] = Sn

p = multivariate_normal.pdf(points, mean=post['mu'], cov=post['Sigma']).reshape(nr, nc)
fig, ax3 = plt.subplots()
ax3.contour(X1, X2, p)
ax3.set_ylim([-1, 1])
ax3.set_xlim([-1, 1])
ax3.set_title('post after 10 observation')
pml.savefig("gauss_2d_update_post.pdf")

#fig.savefig("gauss2dUpdatePostSubplot.pdf")


# exercise 3.1.5
import numpy as np
import scipy.linalg as linalg
from similarity import similarity

from ex3_1_4 import *

# Query vector
q = np.array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0])
# notice, that you could get the query vector using the vectorizer, too:
#q = vectorizer.transform(['matrix rank solv'])
#q = np.asarray(q.toarray())
# or use any other string:
#q = vectorizer.transform(['Can I Google how to fix my problem?'])
#q = np.asarray(q.toarray())

# Method 1 ('for' loop - slow)
N = np.shape(X)[0]; # get the number of data objects
sim = np.zeros((N,1)) # allocate a vector for the similarity
for i in range(N):
    x = X[i,:] # Get the i'th data object (here: document)
    sim[i] = q/linalg.norm(q) @ x.T/linalg.norm(x) # Compute cosine similarity

# Method 2 (one line of code with no iterations - faster)
sim = (q @ X.T).T / (np.sqrt(np.power(X,2).sum(axis=1)) * np.sqrt(np.power(q,2).sum()))

# Method 3 (use the "similarity" function)
sim = similarity(X, q, 'cos');

# Display the result
print('Query vector:\n {0}\n'.format(q))
print('Similarity results:\n {0}'.format(sim))

print('Ran Exercise 3.1.5')


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

#For FIR Filter in section 3

U = np.linspace(-np.pi,np.pi,1000)
V = np.linspace(-np.pi,np.pi,1000)
u,v = np.meshgrid(U,V)
H = 1/81*(1 + np.cos(4*v) + 2*np.cos(3*v) + 2*np.cos(2*v) + 2*np.cos(v)) * (1 + np.cos(4*u) + 2*np.cos(3*u) + 2*np.cos(2*u) + 2*np.cos(u))
H_mag = np.abs(H)
fig = plt.figure()
ax = plt.axes(projection='3d')
surf = ax.plot_surface(u, v, H_mag,cmap='viridis', edgecolor='none')
ax.set_xlabel('u')
ax.set_ylabel('v')
ax.set_zlabel('|H|')
fig.colorbar(surf,location = 'left')
plt.savefig('./output/python_FIR_3.tif')
plt.savefig('./output/python_FIR_3.png')
#plt.show()

#For FIR Filter in section 4
U = np.linspace(-np.pi,np.pi,1000)
V = np.linspace(-np.pi,np.pi,1000)
u,v = np.meshgrid(U,V)
H = 1/25*(1 + 2*np.cos(2*v) + 2*np.cos(v)) * (1 + 2*np.cos(2*u) + 2*np.cos(u))
H_mag = np.abs(H)
fig = plt.figure()
ax = plt.axes(projection='3d')
surf = ax.plot_surface(u, v, H_mag,cmap='viridis', edgecolor='none')
ax.set_xlabel('u')
ax.set_ylabel('v')
ax.set_zlabel('|H|')
fig.colorbar(surf,location = 'left')
plt.savefig('./output/python_FIR_4.tif')
plt.savefig('./output/python_FIR_4.png')
#plt.show()

#For FIR Filter, sharpening filter, in section 4
L = 1.5#lambda
G = 1+L*(1-H)
G_mag = np.abs(G)
fig = plt.figure()
ax = plt.axes(projection='3d')
surf = ax.plot_surface(u, v, G_mag,cmap='viridis', edgecolor='none')
ax.set_xlabel('u')
ax.set_ylabel('v')
ax.set_zlabel('|G|')
fig.colorbar(surf,location = 'left')
plt.savefig('./output/python_FIR_4_sharpening.tif')
plt.savefig('./output/python_FIR_4_sharpening.png')
#plt.show()

#For IIR Filter, in section 5
U = np.linspace(-np.pi,np.pi,1000)
V = np.linspace(-np.pi,np.pi,1000)
u,v = np.meshgrid(U,V)
H = 0.01/(1-0.9*(np.exp(-1j*u)+np.exp(-1j*v)) + 0.81*np.exp(-1j*(u+v)))
H_mag = np.abs(H)
fig = plt.figure()
ax = plt.axes(projection='3d')
surf = ax.plot_surface(u, v, H_mag,cmap='viridis', edgecolor='none')
ax.set_xlabel('u')
ax.set_ylabel('v')
ax.set_zlabel('|H|')
fig.colorbar(surf,location = 'left')
plt.savefig('./output/python_IIR.tif')
plt.savefig('./output/python_IIR.png')
#plt.show()


import numpy as np
from numpy.core.numeric import Inf
import timeit
from scipy.optimize import linprog

# for keeping track of the computatoin time of each step, I record times in a series of arrays
enteringArray = []
exitingArray = []
basisArray = []

# this is a random seed, so as we would produce similar random numbers for the sake of traceablity
#np.random.seed(1)

def random_problem(m, n):
    A = np.concatenate((np.random.randint(2, size=(m, n)), np.identity(m)), axis=1)
    b = np.ones(shape=(m, 1))
    c = np.concatenate((np.ones(shape=(1, n)), np.random.randint(5000000, 5000001, size=(1, m))), axis=1)
    basis = [i for i in range(n, m+n)]
    nonBasis = [i for i in range(0, n)]
    B = A[:, basis]
    N = A[:, nonBasis]
    return A, b, c, basis, nonBasis, B, N

# random problem
rows = 4
columns = 14

# produce a random problem
A, b, c, basis, nonBasis, B, N = random_problem(rows, columns)

# problem defined in our assignment
# A = np.array([[1,0,1,0,0, 1, 0, 0],[0,1,1,1,0,0,1,0],[1,1,0,0,1,0,0,1]])
# b = np.array([[1],[1],[1]])
# c = np.array([[1, 1, 1, 1, 1, 50000000,50000000,50000000]])
# basis = [5,6,7]
# nonBasis = [0,1,2,3,4]
# B = np.array([[1,0,0],[0,1,0],[0,0,1]])
# N = np.array([[1,0,1,0,0], [0,1,1,1,0], [1,1,0,0,1]])

# solve using linprog
startTime = timeit.default_timer()
print(linprog(c, A_eq=A, b_eq=b, method='revised simplex'))
endTime = timeit.default_timer()
print(f"took: {endTime - startTime}")

def entering(c, B, basis, nonBasis, N):
    startEntering = timeit.default_timer()
    status = 0
    cN = c[:, nonBasis]
    cB = c[:, basis]
    reducedCosts = cN - np.matmul(np.matmul(cB, np.linalg.inv(B)), N)
    en = np.argmin(reducedCosts)
    if (reducedCosts[0][en] >= 0):
        status = 1
        endEntering = timeit.default_timer()
        t = endEntering - startEntering
        enteringArray.append(t)
        return status, en, cB, cN, t
    else:
        endEntering = timeit.default_timer()
        t = endEntering - startEntering
        enteringArray.append(t)
        return status, en, cB, cN, t

def exiting(b, B, N, enteringIndex, ):
    startExiting = timeit.default_timer()
    status = 0
    ratioTest = []
    Bb = np.matmul(np.linalg.inv(B), b)
    Ai = np.matmul(np.linalg.inv(B), N[:, enteringIndex])
    for i in range(len(Bb)):
        if (Ai[i]<0):
            ratioTest.append(Inf)
        elif (Ai[i] == 0):
            ratioTest.append(1000000)
        else:
            sth = Bb[i]/Ai[i]
            ratioTest.append(sth[0])
    ratioTest = np.array(ratioTest)
    ex = np.argmin(ratioTest)
    if (ratioTest[ex]>=0):
        endExiting = timeit.default_timer()
        t = endExiting - startExiting
        exitingArray.append(t)
        return status, ex, t
    else:
        status = 3
        endExiting = timeit.default_timer()
        t = endExiting - startExiting
        exitingArray.append(t)
        return status, ex, t

def change_basis(A, en, ex, basis, nonBasis):
    startBasis = timeit.default_timer()
    enteringValue = nonBasis[en]
    nonBasis[en] = basis[ex]
    basis[ex] = enteringValue
    B = A[:, basis]
    N = A[:, nonBasis]
    endBasis = timeit.default_timer()
    t = endBasis - startBasis
    basisArray.append(t)
    return B, N, basis, nonBasis, t

def checkInfeasblity(x, basis, rows, columns):
    for i in range(rows,rows+columns):

        if (i in basis):
            inde = basis.index(i)
            if (x[inde][0] == 0):
                continue
            else:
                return "True"
        else:
            continue
    return "False"

### main body

status = 0
justEntered = 6666

startTime = timeit.default_timer()
while (status != 1):
    status, enteringIndex, cB, cN, t = entering(c, B, basis, nonBasis, N)
    if (status == 1):
        print("Optimal")
        print(f"Function: {np.matmul(np.matmul(cB, np.linalg.inv(B)), b)}")
        x = np.matmul(np.linalg.inv(B), b)
        print(f"x: {x}")
    else:
        status, exitingIndex, t = exiting(b, B, N, enteringIndex)
        B, N, basis, nonBasis, t = change_basis(A, enteringIndex, exitingIndex, basis, nonBasis)

endTime = timeit.default_timer()
print(f"Took: {endTime - startTime}")
print(f"This problem's infeasbility: {checkInfeasblity(x, basis, rows, columns)}")
if (status == 3):
    print(f"This problem's unboundedness: True")
print(f"Average entering: {np.average(enteringArray)}")
print(f"Average exiting: {np.average(exitingArray)}")
print(f"Average changing basis: {np.average(basisArray)}")


# -*- coding: utf-8 -*-
"""Demo160_Distributions.ipynb

## Variable distributions and their effects on Models

Reference
[https://www.statisticssolutions.com/homoscedasticity/]

### Linear Regression Assumptions

- Linear relationship with the outcome Y
- Homoscedasticity
- Normality
- No Multicollinearity

## Linear Assumption

- The X variable is linearly related to the dataset
- Pearson correlation coefficient can determine the linearity magnitude  between variables

## Normality Assumption

- The variable X follows a normal or gaussian distribution

## Homoscedasticity Assumption

- Homogeneity of variance

- Homoscedasticity describes a situation in which the error term (that is, the “noise” or random disturbance in the relationship between the independent variables and the dependent variable) is the same across all values of the independent variables

### Unaffected models

- Neural Networks
- Support Vector Machines
- Trees
"""

import matplotlib.pyplot as plt
import numpy as np
import math
import seaborn as sns
sns.set()
import pandas as pd

import scipy.stats as stats
mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 100)
plt.plot(x, stats.norm.pdf(x, mu, sigma))
plt.show()

mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 1000)
alpha = 0
y = stats.skewnorm.pdf(x, alpha)
sns.lineplot(x,y)

mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 1000)
alpha = 2
y = stats.skewnorm.pdf(x, alpha)
sns.lineplot(x,y)

mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 1000)
alpha = -2
y = stats.skewnorm.pdf(x, alpha)
sns.lineplot(x,y)

mu = 0
variance = 1
sigma = math.sqrt(variance)
x = np.linspace(mu - 5*sigma, mu + 5*sigma, 1000)
fig, ax = plt.subplots()
y = stats.skewnorm.pdf(x, 0)
sns.lineplot(x,y, ax=ax)
y = stats.skewnorm.pdf(x, 3)
sns.lineplot(x,y, ax=ax)
y = stats.skewnorm.pdf(x, -3)
sns.lineplot(x,y, ax=ax)
ax.legend(["Normal", "Negative Skew", "Positive Skew"])

"""## Let's explore the distributions of the variables"""

from google.colab import drive
drive.mount('/content/gdrive')
data = pd.read_csv("gdrive/My Drive/Colab Notebooks/FeatureEngineering/train_date.csv")

data.head()

"""#### Age"""

sns.set_color_codes()
sns.distplot(data['Age'], color='r')

"""Make a QQ-plot:

- Quantiles on vertical axis
- Quantiles of distributin on horizontal axis
- The points on the graph will form a 45 degree line if gaussian
"""

fig, ax = plt.subplots()
stats.probplot(data['Age'].dropna(), dist="norm", plot=ax)
ax.set_title("QQPlot")

sns.set_color_codes()
sns.distplot(data['Age']**(1/1.5), color='g')

fig, ax = plt.subplots()
stats.probplot(data['Age'].dropna()**(1/1.5), dist="norm", plot=ax)
ax.set_title("QQPlot")

"""#### Fare"""

sns.set_color_codes()
sns.distplot(data['Fare'], color='r')

"""Make a QQ-plot:

- Quantiles on vertical axis
- Quantiles of distributin on horizontal axis
- The points on the graph will form a 45 degree line if gaussian
"""

fig, ax = plt.subplots()
stats.probplot(data['Fare'].dropna(), dist="norm", plot=ax)
ax.set_title("QQPlot")

sns.set_color_codes()
sns.distplot(data['Fare']**(1/3), color='g')

fig, ax = plt.subplots()
stats.probplot(data['Fare'].dropna()**(1/1.5), dist="norm", plot=ax)
ax.set_title("QQPlot")


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

r2 = 1 ## r^2 (radius squared)
#n = int(1e5) ## number of elements
n = 10545437.0

## seed PRNG (for debugging)
#print resarsaencfunc()
k = 5
#resarsaencfunc()
#429496729
rnd.seed(k)

## generate random numbers

Xr = rnd.rand(n)

Yr = rnd.rand(n)

Len = rnd.rand(n)

m = 0.

pi = 0.

#print Xr

#print Yr

## calculate whether or not your x, y values are in the circle

for i in range (0,len(Len)):

    if Xr[i]**2 + Yr[i]**2 < r2:
      m = m+1
      pi = 4*(m/n)

## print your approximation of pi

print pi

## optional task: create a scatter plot of the points inside the circle vs the
## points outside of the circle;
## need to use np.where() for this to get the indices
## use plt.scatter() to plot


import re
import numpy as np
from math import log, sqrt
from tqdm import tqdm

def alpha_costant(m):
    if m == 16:
        return 0.673
    if m == 32:
        return 0.697
    if m == 64:
        return 0.709

    if m >= 128:
        return 0.7213/(1+1.079/m)

def leftmost_one(bits):

    result = re.search('0*1', bits)

    return result.end(0)

def get_bucket_size(e):

    m_1 = (1.04/e)**2
    k = int(log(m_1, 2)) + 1

    return 2**k, k

def HyperLogLog(stream, error):

    m, k = get_bucket_size(error)

    M = np.zeros(m, 'd')

    for x in tqdm(stream):

        x = x[:-1]

        j = int(x[:k], 2)

        M[j] = max(M[j], leftmost_one(x[k:]))

    E = alpha_costant(m)*(m**2)/sum(2**(-M))

    # corrections
    if E <= (5/2)*m:

        if 0 in M:
            E = m*log(m/sum(M == 0), 10) # small range correction

            return E, E*1.04/sqrt(m)

        else:
            return E, E*1.04/sqrt(m)

    if E <= (2**64)/30:

        return E, E*1.04/sqrt(m) # no correction

    if E > (2**64)/30:

        E = -(2**64)*log(1-E/(2**64)) # large range correction

        return E, E*1.04/sqrt(m)


# TODO: Check correctness of stationary distribution

from typing import Generic, Mapping, Sequence
import numpy as np
from scipy import linalg
# use math.fsum() instead of sum to get rid of rounding error
import math

from src.type_vars import S, SSf
from src.utils import get_all_states

# Note: if using relative path import like "from ..type_vars import S, SSf", then mp.py should be invoked using -m
# option (https://napuzba.com/a/import-error-relative-no-parent/p4)

class MP(Generic[S]):
    def __init__(self, state_transition_matrix: SSf) -> None:
        try:
            valid: bool = self.check_if_valid(state_transition_matrix)
            if not valid:
                raise ValueError()
        except ValueError:
            exit('Input is not a valid Markov Process')

        self.all_states: Sequence[S] = get_all_states(state_transition_matrix)
        self.state_transition_matrix: SSf = state_transition_matrix

    def check_if_valid(self, state_transition_matrix: SSf) -> bool:
        for s1 in state_transition_matrix:
            if math.fsum(state_transition_matrix[s1].values()) != 1.0:
                return False
            for s2 in state_transition_matrix[s1].keys():
                if state_transition_matrix.get(s1).get(s2) < 0 or state_transition_matrix.get(s1).get(s2) > 1:
                    return False
        return True

    def get_stationary_distribution(self) -> Mapping[S, float]:
        sz = len(self.all_states)
        P = np.zeros((sz, sz), dtype=float)
        # construct state transition matrix as a 2-d np array
        for i, s1 in enumerate(self.all_states):
            for j, s2 in enumerate(self.all_states):
                if self.state_transition_matrix.get(s1) is not None:
                    if self.state_transition_matrix[s1].get(s2) is not None:
                        P[i][j] = self.state_transition_matrix[s1][s2]
        # find a row vector v s.t. v * P = v && sum(v) == 1 by solving an overdetermined linear equation
        # ref: https://stephens999.github.io/fiveMinuteStats/stationary_distribution.html
        a = np.concatenate((np.transpose(P) - np.identity(sz), np.ones((1, sz))), axis=0)
        b = np.concatenate((np.zeros((sz, 1)), np.ones((1, 1))), axis=0)
        x = linalg.lstsq(a, b)[0]
        return {s: x[i][0].astype(float) for i, s in enumerate(self.all_states)}

if __name__ == '__main__':
    state_transition_matrix = {
        1: {1: 0.1, 2: 0.6, 3: 0.1, 4: 0.2},
        2: {1: 0.25, 2: 0.22, 3: 0.24, 4: 0.29},
        3: {1: 0.7, 2: 0.3},
        4: {1: 0.3, 2: 0.5, 3: 0.2}
    }
    mp_obj = MP(state_transition_matrix)
    print(mp_obj.all_states)
    print(mp_obj.get_stationary_distribution())


import numpy as np

def rectangles(interval, f, h):
    a, b = interval
    x = np.linspace(a, b, int((b - a) / h + 1))

    integral = h * np.sum([f((i + j) / 2) for i, j in zip(x, x[1:])])

    return integral

def trapezium(interval, f, h):
    a, b = interval
    x = np.linspace(a, b, int((b - a) / h + 1))
    y = [f(i) for i in x]
    integral = h * sum([i + j for i, j in zip(y[1:], y)]) / 2

    return integral

def simpson(interval, f, size, h):
    a, b = interval
    n = int((b - a) / h + 1)

    first_part = 4 * np.sum([f(a + i * h) for i in range(1, n, 2)])
    second_part = 2 * np.sum([f(a + i * h) for i in range(2, n - 1, 2)])
    integral = h * (f(a) + first_part + second_part + f(b)) / 3

    return integral

def read_interval():
    return list(map(float, input().split()))

def read_h():
    return list(map(float, input().split()))

def runge_romberg(I_h, I_2h, r, p):
    return (I_h - I_2h) / (1  - r ** p)

def main():
    wolfram_val = 0.1454
    interval = read_interval()
    h_series = read_h()
    r = h_series[-1] / h_series[0]
    n = 2
    func = lambda x: x ** 2 / (x ** 2 + 16)
    rect = []
    trap = []
    simp = []

    for h in h_series:
        rectangles_val = rectangles(interval, func, h)
        rect.append(rectangles_val)
        trapezium_val = trapezium(interval, func, h)
        trap.append(trapezium_val)
        simpson_val = simpson(interval, func, n, h)
        simp.append(simpson_val)

    print(f'h = {h_series[0]}\t{r}h = {h_series[-1]}')
    print('rectangles:')
    print(rect, runge_romberg(rect[0], rect[-1], r, n))
    print("trapezium:")
    print(trap, runge_romberg(trap[0], trap[-1], r, n))
    print("simpson:")
    print(simp, runge_romberg(simp[0], simp[-1], r, n))
    print(f'analytical val = {wolfram_val}')
main()


#/usr/bin/env python3
import numpy as np
from numpy import ndarray
from scipy.stats import norm

def condition_mean(x: ndarray, P: ndarray,
                   z: ndarray, R: ndarray, H: ndarray) -> ndarray:
    """compute conditional mean

    Args:
        x (ndarray): initial state
        P (ndarray): initial state covariance
        z (ndarray): measurement
        R (ndarray): measurement covariance
        H (ndarray): measurement matrix i.e. z = H @ x + error

    Returns:
        cond_mean (ndarray): conditioned mean (state)
    """
    return x + P@H.T@np.linalg.inv((H@P@H.T + R))@(z - H@x)

def condition_cov(P: ndarray, R: ndarray, H: ndarray) -> ndarray:
    """compute conditional covariance

    Args:
        P (ndarray): covariance of state estimate
        R (ndarray): covariance of measurement
        H (ndarray): measurement matrix

    Returns:
        ndarray: the conditioned covariance
    """
    return P - P@H.T@np.linalg.inv(H@P@H.T + R)@H@P

def get_task_2f(x_bar: ndarray, P: ndarray,
                z_c: ndarray, R_c: ndarray, H_c: ndarray,
                z_r: ndarray, R_r: ndarray, H_r: ndarray
                ):
    """get state estimates after receiving measurement c or measurement r

    Args:
        x_bar (ndarray): initial state estimate
        P (ndarray): covariance of x_bar
        z_c (ndarray): measurement c
        R_c (ndarray): covariance of measurement c
        H_c (ndarray): measurement matrix i.e. z_c = H_c @ x + error
        z_r (ndarray): measurement r
        R_r (ndarray): covariance of measurement r
        H_r (ndarray): measurement matrix i.e. z_r + H_c @ x + error

    Returns:
        x_bar_c (ndarray): state estimate after measurement c
        P_c (ndarray): covariance of x_bar_c
        x_bar_r (ndarray): state estimate after measurement r
        P_r (ndarray): covariance of x_bar_r
    """
    x_bar_c = condition_mean(x_bar, P, z_c, R_c, H_c)
    P_c     = condition_cov(P, R_c, H_c)

    x_bar_r = condition_mean(x_bar, P, z_r, R_r, H_r)
    P_r     = condition_cov(P, R_r, H_r)

    return x_bar_c, P_c, x_bar_r, P_r

def get_task_2g(x_bar_c: ndarray, P_c: ndarray,
                x_bar_r: ndarray, P_r: ndarray,
                z_c: ndarray, R_c: ndarray, H_c: ndarray,
                z_r: ndarray, R_r: ndarray, H_r: ndarray):
    """get state estimates after receiving measurement c and measurement r

    Args:
        x_bar_c (ndarray): state estimate after receiving measurement c
        P_c (ndarray): covariance of x_bar_c
        x_bar_r (ndarray): state estimate after receiving measurement r
        P_r (ndarray): covariance of x_bar_r
        z_c (ndarray): measurement c
        R_c (ndarray): covariance of measurement c
        H_c (ndarray): measurement matrix i.e. z_c = H_c @ x + error
        z_r (ndarray): measurement r
        R_r (ndarray): covariance of measurement r
        H_r (ndarray): measurement matrix i.e. z_r = H_r @ x + error

    Returns:
        x_bar_cr (ndarray): state estimate after receiving z_c then z_r
        P_cr (ndarray): covariance of x_bar_cr
        x_bar_rc (ndarray): state estimate after receiving z_r then z_c
        P_rc (ndarray): covariance of x_bar_rc
    """
    x_bar_cr = condition_mean(x_bar_c, P_c, z_r, R_r, H_r)
    P_cr = condition_cov(P_c, R_r, H_r)

    x_bar_rc = condition_mean(x_bar_r, P_r, z_c, R_c, H_c,)
    P_rc = condition_cov(P_r, R_c, H_c)

    return x_bar_cr, P_cr, x_bar_rc, P_rc

def get_task_2h(x_bar_rc: ndarray, P_rc: ndarray):
    """get the probability that the boat is above the line

    Args:
        x_bar_rc (ndarray): state
        P_rc (ndarray): covariance

    Returns:
        prob_above_line: the probability that the boat is above the line
    """
    # z = x2 - x1 => Pr(above line) = Pr(z > 5) = 1 - P(z < 5) = 1 - CDF_z(5)
    # Turning multivariate gaussian to a scalar gaussian by evaluating along the
    # normal of the line: z = [-1 1] * [x1 x2]^T
    # The area of this normal above the line is exactly Pr(above line)!

    n = np.array([-1, 1]).reshape((2, 1))

    mean = n.T@x_bar_rc
    std = np.sqrt(n.T@P_rc@n)

    prob_above_line = 1 - norm.cdf(5.0, mean, std)

    return prob_above_line.squeeze()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Feb 10 16:36:43 2019

@author: alankar
"""

import numpy as np
from scipy.special.orthogonal import p_roots #Legendre Polynomial roots
from scipy import constants

def gauss_quad(func,a,b,n,*args):#Legendre
    [x,w] = p_roots(n+1)
    I_G = 0.5*(b-a)*np.sum(w*func(0.5*(b-a)*x+0.5*(b+a),*args))
    return I_G

N = 100

integ = lambda theta:(np.tan(theta)**3/np.cos(theta)**2)/(np.exp(np.tan(theta))-1)

T  = 1e3
print('Using %d points Gaussian (Legendre) Quadrature (T=%.1f K):\n'%(N,T))
W = ((constants.k**4*T**4)/(4*np.pi**2*constants.c**2*constants.hbar**3))*gauss_quad(integ,0,np.pi/2,N)
error = np.abs(((constants.k**4*T**4)/(4*np.pi**2*constants.c**2*constants.hbar**3))*gauss_quad(integ,0,np.pi/2,20*N)-W)
print('W = %6e W⋅m^−2\nError Estimate: %.4e\n'%(W,error))
print('Stefan-Boltzmann Constant Calculated: %.6e W⋅m^−2⋅K^−4'%(W/T**4))
print('Stefan-Boltzmann Constant from Literature: %.e W⋅m^−2⋅K^−4\n'%constants.Stefan_Boltzmann)

"""
Output:

Using 100 points Gaussian (Legendre) Quadrature (T=1000.0 K):

W = 5.670367e+04 W⋅m^−2
Error Estimate: 6.3883e-09

Stefan-Boltzmann Constant Calculated: 5.670367e-08 W⋅m^−2⋅K^−4
Stefan-Boltzmann Constant from Literature: 6e-08 W⋅m^−2⋅K^−4
"""


import numpy as np

class LinearRegression:
    def __init__(self, alpha=0.01, n_iters=1000):
        self.alpha = alpha
        self.n_iters = n_iters
        self.theta = None
        self.bias = None

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.bias = 0
        self.theta = np.zeros((n_features, 1))

        for _ in range(self.n_iters):
            y_pred = self.bias + np.dot(self.theta, X.T)

            dw = np.dot((y_pred - y), X) / n_samples
            db = np.sum(y_pred - y) / n_samples

            self.theta -= self.alpha * dw
            self.bias -= self.alpha * db

    def predict(self, X):
        return self.bias + np.dot(self.theta, X.T)

class LogisticRegression:
    def __init__(self, alpha=0.01, n_iters=1000):
        self.alpha = alpha
        self.n_iters = n_iters
        self.theta = None
        self.bias = None

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.bias = 0
        self.theta = np.zeros((n_features, 1))

        for _ in range(self.n_iters):
            linear_model = self.bias + np.dot(self.theta, X.T)
            y_pred = self._sigmoid(linear_model)

            dw = np.dot((y_pred - y), X) / n_samples
            db = np.sum(y_pred - y) / n_samples

            self.theta -= self.alpha * dw
            self.bias -= self.alpha * db

    def predict(self, X):
        linear_model = self.bias + np.dot(self.theta, X.T)
        y_pred = self._sigmoid(linear_model)[0]
        return [1 if i > 0.5 else 0 for i in y_pred]

    def _sigmoid(self, z):
        return 1 / (1 + np.exp(-z))


import numpy as np
import matplotlib.pylab as plt

def numerical_diff(f, x):
    h = 1e-4 # 0.0001
    return (f(x+h) - f(x-h)) / (2*h)

def function_1(x):
    return 0.01*x**2 + 0.1*x

x = np.arange(0.0, 20.0, 0.1)
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x,y)
plt.show()

print(numerical_diff(function_1, 5))
print(numerical_diff(function_1, 10))


import numpy as np

def qr_mgs_decompose(matrix: np.array) -> (np.array, np.array):
    """
    For n x m matrix return Q1 and R1 components of QR decomposition using
    the modified Gram-Schmidt process, where R1 is n x n upper triangular
    and Q1 is m x n and have orthogonal columns.
    """
    n = matrix.shape[1]
    q1 = np.array(matrix, dtype='float64')
    r1 = np.zeros((n, n))
    for k in range(n):
        a_k = q1[..., k]
        r1[k,k] = np.linalg.norm(a_k)
        a_k /= r1[k, k]
        for i in range(k+1, n):
            a_i = q1[..., i]
            r1[k,i] = np.transpose(a_k) @ a_i
            a_i -= r1[k, i] * a_k
    return q1, r1

def test_diagonal():
    q, r = qr_mgs_decompose(np.diag([-1, 2, 4]))
    assert np.allclose(q, np.diag([-1, 1, 1]))
    assert np.allclose(r, np.diag([1, 2, 4]))

def test_orthogonal():
    orthogonal = np.array([[1, 1, 1, 1], [1, 1, -1, -1], [1, -1, 1, -1]]).T
    q, r = qr_mgs_decompose(orthogonal)
    assert np.allclose(q, 0.5 * orthogonal)
    assert np.allclose(r, np.diag([2, 2, 2]))

def test_matrix_eq_qr_and_q_orthogonal():
    matrix = np.array([[1, 0, -1], [-2, 1, 3], [5, 4, -3], [3, 0, -4]])
    q, r = qr_mgs_decompose(matrix)
    assert np.allclose(matrix, q @ r)
    assert np.allclose(q.T @ q, np.eye(3))


'''
Logistic Regression implementation

more informations:
- http://ufldl.stanford.edu/tutorial/supervised/LogisticRegression/
- https://beckernick.github.io/logistic-regression-from-scratch/
'''

import numpy as np

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

'''
Compute log likelihood (cost function) on data set of size n
@param x - matrix (n * nb_features) matrix of features for each example
@param y - vector (n) vector of labels, on for each example
@param w - vector (nb_features) vector of weights
'''
def log_likelihood(x, y, w):
    z = np.dot(x, w)
    return - np.sum(y*z - np.log(1 + np.exp(z)))

'''
Apply stochastic gradient descent on the whole training set of size n
@param x - matrix (n * nb_features) matrix of features for each example
@param y - vector (n) vector of labels, on for each example
@param w - vector (nb_features) vector of weights
@param lr - learning rate
@return  vector(nb_features) updated weights
'''
def sgd(x, y, w, lr):

    y_hat = sigmoid(np.dot(x, w))
    dW = np.dot(x.T, y_hat - y)
    w = w - lr * dW
    return w

'''
Apply stochastic_gradient_descent on the whole training set of size n, using mini_batches
@param x - matrix (n * nb_features) matrix of features for each example
@param y - vector (n) vector of labels, on for each example
@param w - vector (nb_features) vector of weights
@param lr - learning rate
@param batch_size - size of each batch
@return  vector(nb_features) updated weights
'''
def sgd_mini_batch(X, y, w, lr, batch_size):

    n = X.shape[0]

    for k in range(0, n, batch_size):
        X_batch = X[k:k + batch_size]
        y_batch = y[k:k + batch_size]
        m = len(X_batch)

        y_hat = sigmoid(np.dot(X_batch, w))
        dW = np.dot(X_batch.T, y_hat - y_batch)
        w = w - (lr / m) * dW
    return w

def evaluate(X, y, w):

    y_hat = np.round(sigmoid(np.dot(X, w)))
    succ = int(np.sum(y_hat == y))
    total = X.shape[0]
    perc = succ * 100 / total

    print('Cost: ' + str(log_likelihood(X, y, w)))
    print('Results: {} / {} ({}%)'.format(succ, total, perc))

'''
@param X_train - matrix (n * nb_features) matrix of features (training set)
@param y_train - vector (n) vector of labels (training set)
@param x_test - matrix (n * nb_features) matrix of features (test set)
@param y_test - vector (n) vector of labels (test set)
@param epochs - number of epochs of learning
@param use_intercept - if true, add a bias to the weights
@param batch_size - size of batch size for gradient descent, if -1, gradient descent is on the full dataset

Run the training for several epochs.
After each epochs, the wieghts are tested on the training test and the test set

'''
def train(X_train, y_train, X_test, y_test, epochs, lr, use_intercept = False, batch_size = -1):

    if use_intercept:
        X_train = np.hstack((np.ones((X_train.shape[0], 1)), X_train))
        X_test = np.hstack((np.ones((X_test.shape[0], 1)), X_test))
    w = np.zeros(X_train.shape[1])

    #Training
    for i in range(1, epochs + 1):
        print('Epoch :' + str(i))
        if batch_size == -1:
            w = sgd(X_train, y_train, w, lr)
        else:
            w = sgd_mini_batch(X_train, y_train, w, lr, batch_size)
        print('Train:')
        evaluate(X_train, y_train, w)
        print('Test:')
        evaluate(X_test, y_test, w)

    return w

if __name__ == '__main__':
    X_train, y_train, X_test, y_test = dataset_mnist.load_mnist_bin()

    #train_sclearn(X_train, y_train, X_test, y_test)
    train(X_train, y_train, X_test, y_test, 30, 0.001, use_intercept = True, batch_size = -1)


import numpy as np

def calculate_area(radius):

    """
    Calculates the area of a circle

    Parameters
    ----------
    radius : number or array
        The radius of a circle or many circles

    Returns
    -------
    area : the calculated area/s

    Examples
    --------
    >>> calculate_area(1)
    3.141592653589793
    """
    area = np.pi * radius ** 2
    return area

def calculate_circ(radius):
    """
    Calculates the circumference of a circle

    Parameters
    ----------
    radius : number or array
        The radius of a circle or many circles

    Returns
    -------
    area : the calculated circumference/s

    Examples
    --------
    >>> calculate_circ(1)
    3.141592653589793
    """

    circ = 2 * np.pi * radius
    return circ


import numpy as np

def hms2dec(h, m, s):
  return 15*(h + m/60 + s/3600)

def dms2dec(d, m, s):
  if (d >= 0):
    return d + m/60 + s/3600
  else:
    return d - m/60 - s/3600

def import_bss():
  file = 'bss.dat'
  lines = np.loadtxt(file, usecols=range(1, 7))

  count=1
  result = [ ]
  for line in lines:
    result.append((count, hms2dec(line[0], line[1], line[2]), dms2dec(line[3], line[4]
, line[5])))
    count += 1
  return result

def import_super():
  file = 'super.csv'
  lines = np.loadtxt(file, delimiter=',', skiprows=1,usecols=[0,1])
  result = []
  count = 1
  for line in lines:
    result.append((count, line[0], line[1]))
    count += 1
  return result

def angular_dist(a1, d1, a2, d2):
  a1 = np.radians(a1)
  d1 = np.radians(d1)
  a2 = np.radians(a2)
  d2 = np.radians(d2)

  p1 = np.sin(abs(d1-d2)/2)**2
  p2 = np.cos(d1)*np.cos(d2)*np.sin(abs(a1-a2)/2)**2
  p3 = 2*np.arcsin(np.sqrt(p1+p2))
  return np.degrees(p3)

def crossmatch(cat1, cat2, max_dist):
  matches = []
  nomatches = []

  for cat1line in cat1:
    found = False
    best_match = (0,0,max_dist+1)

    for cat2line in cat2:
      dist = angular_dist(cat1line[1], cat1line[2], cat2line[1], cat2line[2])
      if(dist <= best_match[2]):
        best_match = (cat1line[0], cat2line[0], dist)

    if(best_match[2] <= max_dist):
      matches.append( best_match )
    else:
      nomatches.append( cat1line[0] )

  return (matches, nomatches)

# You can use this to test your function.
# Any code inside this `if` statement will be ignored by the automarker.
if __name__ == '__main__':
  bss_cat = import_bss()
  super_cat = import_super()

  # First example in the question
  max_dist = 40/3600
  matches, no_matches = crossmatch(bss_cat, super_cat, max_dist)

  print(matches[:3])
  print(no_matches[:3])

  # Second example in the question
  max_dist = 5/3600
  matches, no_matches = crossmatch(bss_cat, super_cat, max_dist)
  print(matches)
  print(matches[:3])
  print(no_matches[:3])
  print(len(no_matches))


#utf-8
#Exercício 2 do curso de Data science com Python do prof. Walisson Silva

'''''('1)Crie arrays 1D, 2D e 3D tendo como base as dimensões dos arrays apresentados na figura abaixo. Em seguida, analise cada um dos atributos mencionados na aula.

**OBS.: Note que, nos arrays 1D e 2D da figura, há um pulo do elemento 3 para o 5. Considere que o próximo elemento é o 4.**

![Figura da primeira questão](aula2-q1.png)

Observe um exemplo de resposta no que se refere aos arrays que você deve construir. Lembre-se que você ainda precisa analisar os atributos desses arrays.')'''

import numpy as np

print()
print('=' * 39)
print('Ex.1')
print(np.arange(0, 6))                  # array de 1 dimensão
print(type(np.arange(0, 6)))
print(f'Formato => {np.arange(0, 6).shape}')
print(f'Tamanho => {np.arange(0, 6).size}')
print(f'Dimensão => {np.arange(0, 6).ndim}')
print(f'\n{2}')

b = np.random.randint(3, size=(3, 4))   # array de 2 dimensões
print(b)
print(type(b))
print(f'Formato => {b.shape}')
print(f'Tamanho => {b.size}')
print(f'Dimensão => {b.ndim}')
print(f'\n{3}')

c = np.random.randint(2, size=(3, 3, 5))
print(c)
print(type(c))
print(f'Formato => {c.shape}')
print(f'Tamanho => {c.size}')
print(f'Dimensão => {c.ndim}')

'''2) Crie uma matriz identidade com dimensões 5 X 5.'''

print('=' * 39)
print('Ex.2')
print(np.identity(5, dtype=float))

'''3) Crie uma matriz 3 X 3 com todos os elementos iguais a 1.'''

print('=' * 39)
print('Ex.3')
print(np.ones((3, 3)))

'''4) Crie uma matriz $2 \times 3$ com elementos de valores aleatórios entre 0 e 20.'''

print('=' * 39)
print('Ex.4')
print(np.random.rand(2, 3))

'''5) Crie o vetor [9, 8, 7, 6, 5, 4, 3, 2, 1] utilizando as duas funções vistas na
aula que são capazes de criar um vetor como este, de forma automática.'''

print('=' * 39)
print('Ex.5')
print(np.arange(9, 0, -1))
print(np.linspace(9, 1, 9, dtype=int))


import numpy as np

def sigmoid_function(x):
    return 1.0 / (1.0 + np.exp(-x))

def sigmoid_function_derivative(x):
    return x * (1.0 - x)

def bipolar_sigmoid_function(x):
    return 2.0 / (1.0 + np.exp(-x)) - 1.0

def bipolar_sigmoid_function_derivative(x):
    return (1.0 - np.square(x)) / 2.0

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

def hyperbolic_tangent_derivative(x):
    return 1.0 - np.square(x)

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

def relu_derivative(x):
    x_derivative = np.copy(x)
    x_derivative[x <= 0] = 0
    x_derivative[x > 0] = 1
    return x_derivative

def softmax_function(x):
    """
    Implementation for softmax function

    See:
    http://cs231n.github.io/linear-classify/#softmax
    """

    e_x = np.exp(x - np.max(x, axis=0))
    return e_x / e_x.sum(axis=0)

def leaky_relu(alpha=0.3):
    """
    Implementation of leaky relu activation function
    """

    def leaky_relu(x):
        return np.maximum(alpha * x, x)
    return leaky_relu

def leaky_relu_derivative(alpha=0.3):
    """
    Derivative of leaky relu activation function
    """

    def leaky_relu_derivative(x):
        x_derivative = np.copy(x)
        x_derivative[x <= 0] = alpha
        x_derivative[x > 0] = 1

        return x_derivative

    return leaky_relu_derivative


from utils import r
from sympy import lambdify, symbols, sin, sqrt

formulas = {
    'forward': [
        # First Derivative
        [
            [
                'f\'(x_i)',
                '\\frac{f(x_{i+1})+f(x_i)}{h}+O(h)',
                [0, 1],
                lambda y, h: (y[1] + y[0])/h,
                '1st derivative forward divided differential with $O(h)$'
            ],
            [
                'f\'(x_i)',
                '\\frac{-f(x_{i+2})+4f(x_{i+1})-3f(x_i)}{2h}+O(h^2)',
                [0, 1, 2],
                lambda y, h: (-1*y[2] + 4*y[1] - 3*y[0])/(2*h),
                '1st derivative forward divided differential with $O(h^2)$'
            ],
        ],
        # Second Derivative
        [
            [
                'f\'\'(x_i)',
                '\\frac{f(x_{i+2})-2f(x_{i+1})+f(x_i)}{h^2}+O(h)',
                [0, 1, 2],
                lambda y, h: (y[2]-2*y[1]+y[0])/h**2,
                '2nd derivative forward divided differential with $O(h)$'
            ],
            [
                'f\'\'(x_i)',
                '\\frac{-f(x_{i+3})+4f(x_{i+2})-5f(x_{i+1})+2f(x_i)}{h^2}+O(h^2)',
                [0, 1, 2, 3],
                lambda y, h: (-1*y[3]+4*y[2]-5*y[1]+2*y[0])/(h**2),
                '2nd derivative forward divided differential with $O(h^2)$'
            ],
        ],
    ],
    'backward': [
        # First Derivative
        [
            [
                'f\'(x_i)',
                '\\frac{f(x_{i})-f(x_{i-1})}{h}+O(h)',
                [-1, 0],
                lambda y, h: (y[0] - y[-1])/h,
                '1st derivative backward divided differential with $O(h)$'
            ],
            [
                'f\'(x_i)',
                '\\frac{3f(x_i)-4f(x_{i-1})+f(x_{i-2})}{2h}+O(h^2)',
                [-2, -1, 0],
                lambda y, h: (y[-2] - 4*y[-1] + 3*y[0])/(2*h),
                '1st derivative backward divided differential with $O(h^2)$'
            ],
        ],
        # Second Derivative
        [
            [
                'f\'\'(x_i)',
                '\\frac{f(x_i)-2f(x_{i-1})+f(x_{i-2})}{h^2}+O(h)',
                [-2, -1, 0],
                lambda y, h: (y[0]-2*y[-1]+y[-2])/h**2,
                '2nd derivative backward divided differential with $O(h)$'
            ],
            [
                'f\'\'(x_i)',
                '\\frac{2f(x_i)-5f(x_{i-1})+4f(x_{i-2})-f(x_{i-3})}{h^2}+O(h^2)',
                [-3, -2, -1, 0],
                lambda y, h: (-1*y[-3]+4*y[-2]-5*y[-1]+2*y[0])/(h**2),
                '2nd derivative backward divided differential with $O(h^2)$'
            ],
        ],
    ],
    'center': [
        # First Derivative
        [
            [
                'f\'(x_i)',
                '\\frac{f(x_{i+1})-f(x_{i-1})}{2h}+O(h^2)',
                [-1, 1],
                lambda y, h: (y[1] - y[-1])/h,
                '1st derivative centered divided differential with $O(h^2)$'
            ],
            [
                'f\'(x_i)',
                '\\frac{-f(x_{i+2})+8f(x_{i+1})-8f(x_{i-1}+f(x_{i-2}))}{12h}+O(h^4)',
                [-2, -1, 1, 2],
                lambda y, h: (-1*y[2] + 8*y[1] - 8*y[-1] + y[-2])/(12*h),
                '1st derivative centered divided differential with $O(h^4)$'
            ],
        ],
        # Second Derivative
        [
            [
                'f\'\'(x_i)',
                '\\frac{f(x_{i+1})-2f(x_{i})+f(x_{i-1})}{h^2}+O(h^2)',
                [-1, 0, 1],
                lambda y, h: (y[1] - 2*y[0] + y[-1])/h**2,
                '2nd derivative centered divided differential with $O(h^2)$'
            ],
            [
                'f\'\'(x_i)',
                '\\frac{-f(x_{i+2})+16f(x_{i+1})-30f(x_i)+16f(x_{i-1})-f(x_{i-2})}{12h^2}+O(h^4)',
                [-2, -1, 0, 1, 2],
                lambda y, h: (-1*y[2] + 16*y[1] - 30*y[0] + 16*y[-1] - y[-2])/(12*h**2),
                '2nd derivative centered divided differential with $O(h^4)$'
            ],
        ],
    ],
}

def numerical_differentiation(x_vals, y_vals, e=None, i_vals=None, methods=None, d=None, o=None):
    out_str = ''

    if (i_vals == None and e == None) or (i_vals == None and x_vals.index(e) == -1):
        raise ValueError('value value of i_vals or e should be provided')

    if i_vals == None:
        i = x_vals.index(e)
        i_vals = list(range(-1*i, len(x_vals)-i))

    if methods == None:
        if i_vals[0] == 0:
            methods = ['forward']
        elif i_vals[-1] == 0:
            methods = ['backward']
        else:
             methods = ['center']

    if type(methods) is str:
        methods = [methods]

    if d == None:
        d = [0, 1]
    if type(d) is int:
        d = [d]

    if o == None:
        o = [0, 1]
    if type(o) is int:
        o = [o]

    h = x_vals[1] - x_vals[0]

    out_str += f"\\begin{{center}}\n\\begin{{tabular}}{{ c |{' c' * len(i_vals)} }}\n"
    out_str += f" $i$ & ${'$ & $'.join([str(i) for i in i_vals])}$ \\\\\n \\hline\n"
    out_str += f" $x_i$ & ${'$ & $'.join([str(i) for i in x_vals])}$ \\\\\n"
    out_str += f" $f(x_i)$ & ${'$ & $'.join([str(r(i)) for i in y_vals])}$ \\\\\n"
    out_str += "\\end{tabular}\n\\end{center}\n\n"

    for method in methods:
        derivatives = [e for i, e in enumerate(formulas[method]) if i in d]
        for derivative in derivatives:
            orders = [e for i, e in enumerate(derivative) if i in o]
            for order in orders:
                if len(set(order[2])-set(i_vals)) < 1:
                    out_str += f'\\textbf{{{order[4]}}}\n\n'
                    out_str += f'$$ {order[0]} = {order[1]} $$ \n'
                    out_str += f'$$ {order[0]} = {r(order[3](dict(zip(i_vals, y_vals)), h))} $$ \n\n'

    return out_str

def numerical_differentiation_fn(f, x, h, method, derivative, order):
    i_vals = formulas[method][derivative-1][order-1][2]
    x_vals = [x + i * h for i in range(i_vals[0], i_vals[-1]+1)]
    y_vals = [f(x) for x in x_vals]

    return numerical_differentiation(x_vals, y_vals, h, x, i_vals)

if __name__ == '__main__':
    x = symbols('x')

    t = [0, 25, 50, 75, 100, 125]
    v = [0, 32, 58, 78, 92, 100]

    t = [.4, .6, .8, 1]
    v = [lambdify((x,), sin(0.5*sqrt(x))/x)(i) for i in t]

    print(numerical_differentiation(t, v, e=1))

    # for ti in t:
    #     print(f'\n\n\\textbf{{At $t={ti}$}}\n\n')
    #     print(numerical_differentiation(t, y, e=ti))


###############################################
# Matrix inversion by LU factorization method #
#               Radin Shayanfar               #
#                   9731032                   #
###############################################

from typing import Any, Tuple

import numpy as np

def lu_factor(aMat: np.ndarray) -> Tuple[Any, Any]:
    n = aMat.shape[0]
    uMat = aMat.copy()
    lMat = np.eye(n, dtype=np.double)
    for i in range(n):
        sliced = uMat[i:, i:]

        lMat[i + 1:, i] = sliced[1:, 0] / sliced[0][0]
        for r_ind in range(1, sliced.shape[0]):
            coef = -1 * sliced[r_ind][0] / sliced[0][0]
            sliced[r_ind] = sliced[r_ind] + coef * sliced[0]

        uMat[i:, i:] = sliced

    return lMat, uMat

def forward_sub(augmented: np.ndarray) -> np.ndarray:
    for i in range(augmented.shape[0]):
        augmented[i + 1:, -1] -= augmented[i + 1:, i] * augmented[i, -1]

    return augmented[:, -1]

def backward_sub(augmented: np.ndarray) -> np.ndarray:
    for i in reversed(range(augmented.shape[0])):
        augmented[i, -1] /= augmented[i, i]
        augmented[:i, -1] -= augmented[:i, i] * augmented[i, -1]

    return augmented[:, -1]

def solve_with_lu(l: np.ndarray, u: np.ndarray, b: np.ndarray) -> np.ndarray:
    augmented = np.concatenate((l, b[..., np.newaxis]), axis=1)
    y = forward_sub(augmented)
    augmented = np.concatenate((u, y[..., np.newaxis]), axis=1)
    return backward_sub(augmented)

def inverse_matrix(l: np.ndarray, u: np.ndarray) -> np.ndarray:
    n = l.shape[0]
    I = np.eye(n)
    inv = np.zeros((n, n), dtype=np.double)
    for i in range(n):
        inv[:, i] = solve_with_lu(l, u, I[:, i])

    return inv

if __name__ == '__main__':
    n = int(input("Enter dimension:\n"))
    print("Enter A matrix:")
    a = []
    for i in range(n):
        a.append(list(map(float, input().split())))
    a = np.array(a, dtype=np.double)

    l, u = lu_factor(a)
    print(f"L matrix:\n{l}")
    print("--------------")
    print(f"U matrix:\n{u}")
    print("--------------")

    print(f"A inverse:\n{inverse_matrix(l, u)}")