import sympy as sym
import numpy as np
import matplotlib.pyplot as plt

class Newton():

    def __init__(self, f='x**2', max_iter=1e6, eps=1e-14):
        """ Newton method class to find the solution

        Attributes:
                f (str) representing the function
                max_iter (int) representing the maximum number of iterations to find the solution
                eps (float) representing stopping criteria abs(f(x)) < epsilon

            Examples
            --------
            >>> f = '2*x**2 - 50'
            >>> newton = Newton(f)
            >>> newton.find_solution(1)
            x = 13.0, f(x) = 288.0, iteration #1
            x = 7.461538461538462, f(x) = 61.349112426035504, iteration #2
            x = 5.406026962727994, f(x) = 8.45025504348412, iteration #3
            x = 5.015247601944898, f(x) = 0.3054170176281019, iteration #4
            x = 5.000023178253949, f(x) = 0.00046356615344222973, iteration #5
            x = 5.000000000053723, f(x) = 1.0744685141617083e-09, iteration #6
            x = 5.0, f(x) = 0.0, iteration #7

            Solution found at 5.0 with 7 iterations

                """

        self.f = f  # Symbolic expression of the function
        self.max_iter = max_iter
        self.eps = eps
        x = sym.symbols('x')  # Define x as mathematical symbol
        self.x = x

    def calculate_f_value(self, x):
        """ Function to evaluate function for given x

            Args:
                x (float): x value

            Returns:
                f(x) (float): value of the function for the given x

            """

        # lambdify provides a bridge from Sympy expression to numerical libraries
        f = sym.lambdify([self.x], self.f)
        return f(x)

    def calculate_derivative(self, x):
        """ Function to find the derivative and calculate the dfdx value for given x

            Args:
                x (float): x value

            Returns:
                dfdx(x) (float): derivate of f(x) at given x
            """

        self.dfdx_expr = sym.diff(self.f, self.x)
        dfdx = sym.lambdify([self.x], self.dfdx_expr)

        return dfdx(x)

    def plot_function(self, a=-10, b=10):
        """ Function to plot the given function

            Args:
                a, b (float) [optional]: intervals

            Returns:
                None
            """
        x = np.linspace(a, b, 100)
        y = sym.lambdify([self.x], self.f)(x)

        fig = plt.figure()
        ax = fig.add_subplot(1, 1, 1)
        ax.spines['left'].set_position('center')
        ax.spines['bottom'].set_position('zero')
        ax.spines['right'].set_color('none')
        ax.spines['top'].set_color('none')
        ax.xaxis.set_ticks_position('bottom')
        ax.yaxis.set_ticks_position('left')
        plt.plot(x, y)
        plt.show()

    def find_solution(self, x0):
        """ Function to approximate solution of f(x)=0 by Newton's method

            Args:
                x0 (float): initial guess for a solution f(x)=0

            Returns:
                xn (float): intercept (solution) by the formula x = xn - f(xn)/dfdx(xn)

        """
        xn = x0
        iter_counter = 0
        f_value = self.calculate_f_value(xn)
        while abs(f_value) > self.eps and iter_counter < self.max_iter:
            try:
                xn = xn - float(f_value) / self.calculate_derivative(xn)
            except ZeroDivisionError:
                # Handling ZeroDivisonError - if the derivative of the initial guess is 0, increment x0
                print(
                    "Error! - derivative zero for x = {}. Incrementing by 1...".format(xn))
                xn += 1
            f_value = self.calculate_f_value(xn)
            iter_counter += 1

            print("x = {}, f(x) = {}, iteration #{}".format(
                xn, f_value, iter_counter))

        print("Solution found at {} with {} iterations".format(xn, iter_counter))
        if abs(f_value) > self.eps:
            print("Solution not found! Try changing the initial guess")
            iter_counter = -1
            xn = None

        return xn


import numpy as np
from scipy.fftpack import fft, fftshift
import math

"""
A3-Part-3: Symmetry properties of the DFT

Write a function to check if the input signal is real and even using the symmetry properties of its
DFT. The function will return the result of this test, the zerophase windowed version of the input
signal (dftbuffer), and the DFT of the dftbuffer.

Given an input signal x of length M, do a zero phase windowing of x without any zero-padding (a
dftbuffer, on the same lines as the fftbuffer in sms-tools). Then compute the M point DFT of the
zero phase windowed signal and use the symmetry of the computed DFT to test if the input signal x
is real and even. Return the result of the test, the dftbuffer computed, and the DFT of the dftbuffer.

The input argument is a signal x of length M. The output is a tuple with three elements
(isRealEven, dftbuffer, X), where 'isRealEven' is a boolean variable which is True if x is real
and even, else False. dftbuffer is the M length zero phase windowed version of x. X is the M point
DFT of the dftbuffer.

To make the problem easier, we will use odd length input sequence in this question (M is odd).

Due to the precision of the FFT computation, the zero values of the DFT are not zero but very small
values < 1e-12 in magnitude. For practical purposes, all values with absolute value less than 1e-6
can be considered to be zero. Use an error tolerance of 1e-6 to compare if two floating point arrays
are equal.

Caveat: Use the imaginary part of the spectrum instead of the phase to check if the input signal is
real and even.

Test case 1: If x = np.array([ 2, 3, 4, 3, 2 ]), which is a real and even signal (after zero phase
windowing), the function returns (True, array([ 4., 3., 2., 2., 3.]), array([14.0000+0.j, 2.6180+0.j,
0.3820+0.j, 0.3820+0.j, 2.6180+0.j])) (values are approximate)

Test case 2: If x = np.array([1, 2, 3, 4, 1, 2, 3]), which is not a even signal (after zero phase
windowing), the function returns (False,  array([ 4.,  1.,  2.,  3.,  1.,  2.,  3.]), array([ 16.+0.j,
2.+0.69j, 2.+3.51j, 2.-1.08j, 2.+1.08j, 2.-3.51j, 2.-0.69j])) (values are approximate)
"""

def testRealEven(x):
    """
    Inputs:
        x (numpy array)= input signal of length M (M is odd)
    Output:
        The function should return a tuple (isRealEven, dftbuffer, X)
        isRealEven (boolean) = True if the input x is real and even, and False otherwise
        dftbuffer (numpy array, possibly complex) = The M point zero phase windowed version of x
        X (numpy array, possibly complex) = The M point DFT of dftbuffer
    """
    ## Your code here
    M = x.shape[-1]
    hM1 = (M+1)//2
    hM2 = M//2
    dft_buffer = np.zeros(M)
    dft_buffer[:hM1] = x[hM2:]
    dft_buffer[hM1:] = x[:hM2]
    X = fft(dft_buffer)
    X_zeros = 0
    for i in range(M):
        if X.imag[i] >= 1e-6:
            X_zeros += 1
    if X_zeros == 0:
        is_real_even = True
    else:
        is_real_even = False
    return (is_real_even, dft_buffer, X)


import numpy as np
from math import ceil

def findPeak_1d_sf(lst):
    """
    Args:
        lst: list of numbers

    Returns:
        max: A peak number
    """
    max = 0
    for i in range(1,len(lst)-1):
        if i == len(lst)-2:
            if lst[i+1] > max:
                max= lst[i+1]
        elif lst[i-1] < lst[i] and lst[i+1] < lst[i]:
            if lst[i] > max:
                max= lst[i]
    return max

def findPeak_1d_optimized(lst, low, high):
    """
    Args:
        lst: List of numbers
        low: low index
        high: high indes
    Returns:
        A peak number

    """
    n= len(lst)
    middle = low + (high - low) / 2
    middle = int(middle)
    if (middle==0 or lst[middle]>= lst[middle-1]) and\
            (middle==n-1 or lst[middle]>= lst[middle+1]):

        return lst[middle]

    if (middle > 0 and (lst[middle] <= lst[middle -1] and lst[middle -1] >= lst[middle +1])) :
        return findPeak_1d_optimized(lst, low, middle-1)

    elif (middle > 0 and (lst[middle] <= lst[middle +1] and lst[middle +1] >= lst[middle -1])) :
        return findPeak_1d_optimized(lst,middle+1,high)

def findPeak_2d_optimized(arr,mid):
    """

    Args:
        arr: 2D numpy array of numbers
        mid: always equal column//2

    Returns:
        A 2D peak number
    """
    row, col = arr.shape[0], arr.shape[1]
    max =0
    for i in range(row):
        if arr[i][mid] > max:
            max = arr[i][mid]
            max_indx=i
    if (mid == 0 or mid == columns - 1):
        return max

    if (arr[max_indx][mid-1]< arr[max_indx][mid] and arr[max_indx][mid+1]< arr[max_indx][mid]):
        return max

    if (max < arr[max_indx][mid - 1]):
        return findPeak_2d_optimized(arr, mid - ceil(mid / 2.0))

    if (max < arr[max_indx][mid + 1]):
        return findPeak_2d_optimized(arr, mid + ceil(mid / 2.0))

if __name__ == '__main__':
    import time
    arr = np.array([[50, 8, 10, 10],
           [14, 13, 12, 11],
           [15, 9, 11, 21],
           [16, 17, 19, 20]])
    #arr= np.array([[10,20,15], [21,30,14],[7,16,32]])

    # Number of Columns
    rows = 4
    columns = 4
    mid= arr.shape[1]//2
    print(findPeak_2d_optimized(arr,mid))
    #print(findPeak(arr, rows, columns))
"""
    l= [1,2,4,1,20,1,40,3,1,100,4]
    l=[1, 3, 20, 4, 1, 0]
    t1 = time.time()
    x= findPeak_1d_sf(l)
    t2= time.time()
    y= findPeak_1d_optimized(l, 0, len(l)-1)
    #y= findPeak(l, len(l))
    t3= time.time()

    print(y)
    opt= t3 - t2
    fst= t2-t1
    print(opt)
    print(fst)
    print(fst-opt)
    """


#!/usr/bin/env python
"""
    Copyright 2017 by Michael Wild (alohawild)

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

============================================================================================================
This is a pi calculation process using Monte Carlo or simple random process.

Imagine a circle in a box. The edges just touch the box. Lets make it a circle of radius 1.

Thus any point on or within the cirlce are 1 unit or less from the center.

It should be possible to randomly select points in the box and determine if the point is in the circle or not.
The ratio of points in the box over the number of randomly selected should be 1/4*Pi

"""
__author__ = 'michaelwild'
__copyright__ = "Copyright (C) 2018 Michael Wild"
__license__ = "Apache License, Version 2.0"
__version__ = "0.0.3"
__credits__ = ["Michael Wild"]
__maintainer__ = "Michael Wild"
__email__ = "alohawild@mac.com"
__status__ = "Initial"

import os
import sys
import numpy as np
import math

from time import process_time

def howFar(x,y):

    distance = (x * x) + (y * y)
    return np.sqrt(distance)

def runtime(start):

    return process_time() - start

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

program = "Pi Calc"

piLoop = 1000000
inCircle = 0

begin_time = process_time()

# =============================================================
# Main program begins here

print(program)
print("Version ", __version__, " ", __copyright__, " ", __license__)
print("Running on ", sys.version)

for i in range(1, piLoop):
    x = (np.random.uniform()* 2) -1
    y = (np.random.uniform() * 2) -1

    if (howFar(x,y)<1.0) :
        inCircle = inCircle + 1
piGuess = 4.0* (inCircle / piLoop)

piError = math.pi - piGuess

print("Loops:", piLoop)
print("Calculated value: ",piGuess, "Error: ", piError)

finish = runtime(begin_time)
print("Run time:", finish)


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

import numpy as np

ar = np.array

A = ar([[3, 0, 0, 0],
        [2, 1, 0, 0],
        [1, 5, 1, 0],
        [7, 9, 8, 4]])

b = ar([-9, 6, 2, 5])
bb= ar([b, b]).T

b_p = b

n = 4
for i in range(n):
    #xi = b[i] / A[i, i]
    xi = b_p[0] / A[i, i]
    print('x',i, '=', xi)
    b_p = b_p[1:] - (xi * A[i+1:, i])
    print(b_p)

# Algoritmo general
# 1.- Resolvemos L_11 * X_1 = B_1
# 2.- Resolvemos B_2 = B_2 - L_21 * X_1

# Supongamos que tenemos un parámetro de bloque bloque

def elim_gauss_bloque(A, b, n):

    # primer paso: Resolvemos L_11 X_1 = B_1
    X_1 = np.linalg.solve(A[:n, :n], b[:n])
    if  A.shape[0] == n:
        return X_1
    else:
        #print(X_1)

        # 2do paso:  Actualizamos la B
        b = b[n:,] - np.matmul(A[n:, :n], X_1)
        #print(b)
        # Tercer paso:llamada recursiva
        #print('falta:', A[n:, n:], 'b:', b[n:, ])
        return np.append(X_1, elim_gauss_bloque(A[n:, n:], b, n), axis = 0)

A = ar([[3, 0, 0, 0],
        [2, 1, 0, 0],
        [1, 5, 1, 0],
        [7, 9, 8, 4]])

b = ar([[-9, 12], [6, -1], [2, 0], [5, 1]])

elim_gauss_bloque(A, b, n = 1)

elim_gauss_bloque(A, b, n = 2)


"""
Implementation of Linear Regression using the Normal Equation.

Let m = #training examples, n = #number of features and the
input shapes are y is R^(m x 1), X is R^(m x n), w is R^(n x 1).
Using these shapes, the normal equation implementation is
exactly as the derived formula :)

Programmed by Aladdin Persson <aladdin.persson at hotmail dot com>
*    2020-04-25 Initial coding
"""

import numpy as np

def linear_regression_normal_equation(X, y):
    ones = np.ones((X.shape[0], 1))
    X = np.append(ones, X, axis=1)
    W = np.dot(np.linalg.pinv(np.dot(X.T, X)), np.dot(X.T, y))
    return W

if __name__ == "__main__":
    # Run a small test example: y = 5x (approximately)
    m, n = 500, 1
    X = np.random.rand(m, n)
    y = 5 * X + np.random.randn(m, n) * 0.1
    W = linear_regression_normal_equation(X, y)


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

# Function
def f(x):
    return exp(x)

# To compute the first column of Richardson method
def ThreeMidpoint(x0, h):
    df = (f(x0+h)-f(x0-h))/(2*h)
    return df

def Richarson(x0, h, tol):
    R = np.zeros((50,50))

    R[1][1] = ThreeMidpoint(x0, h)

    for i in range(2, 50):
        h = 0.5*h
        R[i][1] = ThreeMidpoint(x0, h)

        for j in range(2, i+1):
            R[i][j] = R[i][j-1] + (1/(pow(4,j-1) - 1)) * (R[i][j-1] - R[i-1][j-1])

        if abs((R[i][i] - R[i-1][i-1])) < tol: break

    return R[i][i]

if __name__ == '__main__':

    x = 0

    print(Richarson(x, 1, 0.01))


#!/usr/bin/env python3
import numpy as np

theta = 0.2
print("Theta: {}".format(theta))
years = 6
r00 = 0.05
print("r00: {}".format(r00))
print("r0n factor: 0.9")
t=6
r0n = [r00]

#Array of interest rates
for i in range(1, t+1):
    r0n.append(r0n[i-1]*0.9)

#Interest rate tree using array and volatility parameter
def rate_tree(r0n,theta):
    ratetree=[]
    for i,element in enumerate(r0n):
        treecolumn=[]
        for j in range(0,i+1):
            treecolumn.append(element*np.exp(2*j*theta))
        ratetree.append(treecolumn)
    return ratetree

#Interest rate tree
r_t = rate_tree(r0n, theta)

#Cashflow tree using only number of period parameter
def cf_tree(t):
    cftree=[]
    for i in range(0, t+1):
        treecolumn=[]
        for j in range(0,i+1):
            if i < t:
                treecolumn.append(0)
            if i == t:
                treecolumn.append(100)
        cftree.append(treecolumn)
    return cftree

#Present value tree
def pv_tree(ratetree,cftree):
    pvtree=[]
    for element in cftree:
        parttree=[]
        for element2 in element:
            parttree.append(0)
        pvtree.append(parttree)
    icol=len(pvtree)-1
    for j in range(0,icol+1):
        pvtree[icol][j]=cftree[icol][j]
    for i in range (1,len(pvtree)):
        icol=len(pvtree)-1-i
        for j in range(0,icol+1):
            pvtree[icol][j]=cftree[icol][j]+(0.5*pvtree[icol+1][j+1]+0.5*pvtree[icol+1][j])/(1+ratetree[icol][j])
    return pvtree

#Define function to calculate spot rates
def sr(pv,periods):
    return (100/pv)**(1/periods)-1

#Create an array with the PV trees for bonds of different durations
pv_array_tree = []
#and the spot rates
spot_array = []
for i in range(1,t+1):
    present_value_tree = pv_tree(rate_tree(r0n, theta),cf_tree(i))
    pv_array_tree.append(present_value_tree)
    spot_rate = sr(pv_array_tree[i-1][0][0],i)
    spot_array.append(spot_rate)

#Calculating forward rates
forward_array = [spot_array[0]]
for i in range(1,t):
    forward_array.append((1+spot_array[i])**(i+1)/(1+spot_array[i-1])**(i)-1)

print()
print("BDT Lattice Rate Tree")
print(r_t)
print()
print("Spot Rates")
print(spot_array)
print()
print("Forward Array")
print(forward_array)


import matplotlib.pyplot as plt
import numpy as np

def s_eitken(z, n):
    s1 = z
    s2 = s1 + 1 / 3 * z ** 2
    s3 = s2 + 1 / 5 * z ** 3
    res = s3 - (s3 - s2) ** 2 / (s3 - s2 - s2 + s1)
    for k in range(4, n + 1):
        s1 = s2
        s2 = s3
        s3 += 1 / (2 * k - 1) * z ** k
        if s3 - s2 - s2 + s1 != 0:
            res = s3 - (s3 - s2) ** 2 / (s3 - s2 - s2 + s1)
    return res

def s(z):
    n = 10 ** 6
    res = 0
    for k in range(1, n + 1):
        res += 1 / (2 * k - 1) * z ** k
    return res

def calculate_value(z, wolfram_value):
    print("--s(" + str(z) + ")--")
    wolfram_value = wolfram_value
    eitken_value = s_eitken(z, 10 ** 3)
    print("wolfram: " + str(wolfram_value))
    print("eitkens method: " + str(eitken_value))
    print("abs of difference: " + str(abs(wolfram_value - eitken_value)))
    print("---")

# генерируем точки на верхней дуге единичной окружности и смотрим модуль разности со значением первых 10 ** 6 членов
# точки на окружности, потому что внутри окружности сходимость быстрее
# можно видеть, что в точке (1; 0) она расходится и график этому соответсвтует
def draw_speed():
    step = 0.01
    x = -1
    y = 0j
    n = 10 ** 3
    data_x = []
    data_y = []
    while x <= 1:
        accurate_value = s(x + y)
        eitken = s_eitken(x + y, n)
        data_x.append(x)
        data_y.append(np.log10(1 / abs(accurate_value - eitken)))
        x += step
        y = np.sqrt(1 - x ** 2) * 1j
    plt.subplot(211)
    plt.title("speed conversion")
    plt.plot(data_x, data_y)
    plt.ylabel("log10(1 / |accurate - eitken|)")
    plt.xlabel("z")

def main():
    plt.figure(1)
    calculate_value(-0.9, -0.720117)
    calculate_value(-1, -0.7853981633974483)
    calculate_value(np.e ** (3j * np.pi / 4), -0.647215 + 0.486294j)
    calculate_value(1j, -0.243747747 + 0.86697299j)
    draw_speed()
    plt.tight_layout()
    plt.show()

if __name__ == "__main__":
    main()


# -*- coding: utf-8 -*-
"""
Created on Wed Jun 16 13:09:00 2021

@author: Luigi
"""
import numpy as np
import scipy as sci
from scipy.linalg import qr
import matplotlib.pyplot as plt

def Usolve(U, b):
    m, n = U.shape
    if m != n:
        print("Matr non quadrata")
        return [], 1
    x = np.zeros((m,))
    x[m-1] = b[m - 1] / U[m-1, n-1]
    for i in range(m - 2, -1, -1):
        x[i] = (b[i] - (np.dot(U[i, i+1 : m], x[i + 1 : m]))) / U[i,i]

    return x, 0

"""
    Qy = b
    Rx = y

"""
def metodoQR(x, y, n):
    H = np.vander(x, n+1)
    Q,R = qr(H)
    y1 = np.dot(Q.T, y)
    a, flag = Usolve(R[:n+1,:], y1[:n+1])
    return a

x = np.arange(1900, 2020, 10, dtype = float)
y = np.array([76,92,106,123,132,151,179,203,226,249,281,305], dtype = float)
print(x)

x1 = metodoQR(x, y, 1)
x2 = metodoQR(x, y, 2)
x3 = metodoQR(x, y, 3)

xx = np.linspace(np.min(x), np.max(x), 100)
pol1 = np.polyval(x1, xx)
pol2 = np.polyval(x2, xx)
pol3 = np.polyval(x3, xx)

plt.plot(x, y, "o", xx, pol1, xx, pol2, xx, pol3)
plt.legend(["Nodi", "Pol. 1", "Pol. 2", "Pol. 3"])
plt.show()

# Punto D
err1 = []
err2 = []
err3 = []
for i in range(12):
    err1.append((pol1[i] - y[i]) ** 2)
    err2.append((pol2[i] - y[i]) ** 2)
    err3.append((pol3[i] - y[i]) ** 2)

plt.plot(np.arange(12), err1, np.arange(12), err2, np.arange(12), err3)
plt.legend(["Err1", "Err2", "Err3"])
plt.show()

""" OSSERVIAMO CHE I POLINOMI DI GRADO 2 E 3 DANNO LUOGO AGLI STESSI ERRORI
"""


# Cowboy Linear Algebra python library at Video #9
# By: Nick the Space Cowboy

import numpy as np

def back_sub(Utri, c):
	'''
	back substitution alogrithm for solving upper triangular system
	'''
	m = len(Utri) # row dimension of the Utri matrix
	n = len(Utri[0]) # column dimension of the Utri matrix
	x = np.zeros([n, 1], dtype=np.float64)
	b = np.array(c, dtype=np.float64)
	t1 = 0
	if m > n:
		# ct1 = m
		# m = n
		raise Exception ("More rows than columns (Use a different method)")
	for i in range(m - 1, -1, -1):	# loop to iterate through row index
		x[i] += b[i] / Utri[i, i]
		for j in range(n - 1 , i, -1):	# loop to iterate through the off diagonal Sum part
			x[i] += (- (Utri[i, j] * x[j])) / Utri[i, i]
	return x

def SGE(A, b=0):
	'''
	function to perform structured gaussian elimination
	If a b vector isn't passed through, a LU decomposition is returned
	'''
	m = len(A)
	n = len(A[0])
	L = np.identity(m, dtype=np.float64)
	U = np.array(A)
	c = np.array(b)
	b_state = isinstance(b, np.ndarray)
	for i in range(0, n, 1):
		for j in range(i+1, m, 1):
			L[j,i] = U[j,i] / U[i,i]
			U[j] = U[j] - (L[j,i] * U[i])
			if b_state == False:
				pass
			else:
				c[j] = c[j] - (L[j,i] * c[i])
	if b_state==False:
		return L, U
	else:
		return U, c

def LU(A):
	'''
	Function to perform LU decomposition
	'''
	return SGE(A)

def LDV(A):
	'''
	Function to perform a LDV Matrix decomposition
	'''
	L, U = LU(A)
	m = len(A)
	n = len(A[0])
	D = np.identity(m, dtype=np.float64)
	for i in range(0, m, 1):
		for j in range(0, n, 1):
			if i == j:
				D[i, j] = U[i, j]
	V = np.dot(np.linalg.inv(D), U)
	return L, D, V

def pos_def_check(A):
	'''
	Checks if a matrix is positive definate
	'''
	pd_state = True
	n = len(A)
	for i in range(1, n):
		if (A[i, i - 1] ** 2) >= A[i, i]:
			raise Exception ("Marix is not positive definate")
			pd_state =  False
	return pd_state

def cholesky(A):
	'''
	Function to perform a Cholesky decompostion
	'''
	pd = pos_def_check(A)
	if pd == True:
		n = len(A)
		L = np.zeros_like(A, dtype=np.float64)
		for i in range(0, n):
			for j in range(0, i + 1):
				sum_part = 0
				if i == j:
					for k in range(0, j):
						sum_part += L[j, k] ** 2
					L[i, j] = np.sqrt(A[j, j] - sum_part)
				else:
					for k in range(0, j):
						sum_part += L[i, k] * L[j, k]
					L[i, j] = (A[i, j] - sum_part) / L[j, j]
	else:
		L = f"Matrix is not positive definate"
	return L

def LDLT(A):
	'''
	Function to perform the LDL^T matrix decomposition
	'''
	C = cholesky(A)
	n = len(A)
	D = np.identity(n, dtype=np.float64)
	L = C
	for i in range(0, n):
		D[i, i] = C[i, i]**2
		L[:, i] = C[:, i] * D[i, i] ** (-1/2)
	return L, D


import numpy as np
from jacobi_iteration_method import jacobi_iteration
from gauss_seidel_iteration import gauss_seidel_iteration

def successive_over_relaxation(A, b, omega, tol=1e-9, Max_iter=5000):
    """ Solve linear equations by successive over relaxation method.

    Args:
        A: ndarray, coefficients matrix
        b: ndarray, constant vector
        omega: double, hyperrelaxation factor
        tol: double, iteration accuracy
        Max_iter: maximum iteration number

    Returns:
        y: ndarray, solution of the linear euqations
        k: int, iteration number
    """
    # decompose coefficients matrix
    D = np.diag(np.diag(A))
    L = -1 * np.tril(A - D)
    U = -1 * np.triu(A - D)

    # construct iteration coefficients matrix
    B = np.linalg.inv(D - omega * L).dot((1 - omega) * D + omega * U)
    f = omega * np.linalg.inv(D - omega * L).dot(b)

    # initial iteration vector
    x = np.ones_like(b)
    # first iteration
    k = 1
    y = B.dot(x) + f

    # iteration
    while np.max(np.abs(y - x)) >= tol and k < Max_iter:
        k += 1
        x = y
        y = B.dot(x) + f

    return (y, k)

def print_solution_and_iteration_number(x, n):
    """ Print the solution and iteration number.

    Args:
        x: ndarray, solution vector
        n: int, iteration number
    """
    for i in range(len(x)):
        print(f"x_{i+1} = {x[i]}")
    print(f"The iteration number is {n}.")

if __name__ == '__main__':
    # coefficients matrix
    A = np.array([[-4, 1, 1, 1], [1, -4, 1, 1], [1, 1, -4, 1], [1, 1, 1, -4]])
    # constant vector
    b = np.ones(4)
    # Jacobi iteration method
    x1, n1 = jacobi_iteration(A, b)
    print("The solution of the linear equations by Jacobi iteration method is:")
    print_solution_and_iteration_number(x1, n1)
    # Gauss Seidel iteration method
    x2, n2 = gauss_seidel_iteration(A, b)
    print("The solution of the linear equations by Gauss Seidel iteration method is:")
    print_solution_and_iteration_number(x2, n2)
    # successive over relaxation method
    # when selecting the hyperrelaxation factor, it should be between 1.2 and 1.3
    # then the minumum iteration number is 21
    x3, n3 = successive_over_relaxation(A, b, 1.2)
    print("The solution of the linear equations by successive over relaxation method is:")
    print_solution_and_iteration_number(x3, n3)


import numpy                 #numpy is a library for array operations akin to MATLAB
from matplotlib import pyplot    #matplotlib is 2D plotting library

def linearconv(nx):
    dx = 2.0 / (nx - 1)
    nt = 20    #nt is the number of timesteps we want to calculate
    dt = .025  #dt is the amount of time each timestep covers (delta t)
    c = 1

    u = numpy.ones(nx)      #defining a numpy array which is nx elements long with every value equal to 1.
    u[int(.5/dx):int(1 / dx + 1)] = 2  #setting u = 2 between 0.5 and 1 as per our I.C.s

    un = numpy.ones(nx) #initializing our placeholder array, un, to hold the values we calculate for the n+1 timestep

    for n in range(nt):  #iterate through time
        un = u.copy() ##copy the existing values of u into un
        for i in range(1, nx):
            u[i] = un[i] - c * dt / dx * (un[i] - un[i-1])
    pyplot.figure()
    pyplot.plot(numpy.linspace(0, 2, nx), u);

# Now let's examine the results of our linear convection problem with an increasingly fine mesh.

linearconv(41) #convection using 41 grid points

linearconv(61)

linearconv(71)

linearconv(85)

# In[8]:

def linearconv(nx):
    dx = 2.0 / (nx - 1)
    nt = 20    #nt is the number of timesteps we want to calculate
    c = 1       # this is the wave speed
    sigma = .5 # This is the condition on the CFL number

    dt = sigma * dx

    u = numpy.ones(nx)
    u[int(.5/dx):int(1 / dx + 1)] = 2

    un = numpy.ones(nx)

    for n in range(nt):  #iterate through time
        un = u.copy() ##copy the existing values of u into un
        for i in range(1, nx):
            u[i] = un[i] - c * dt / dx * (un[i] - un[i-1])

    pyplot.figure()
    pyplot.plot(numpy.linspace(0, 2, nx), u)

linearconv(41)

linearconv(61)

linearconv(81)

linearconv(101)

linearconv(121)

# Notice that as the number of points `nx` increases, the wave convects a shorter and shorter distance.
#  The number of time iterations we have advanced the solution at is held constant at `nt = 20`,
# but depending on the value of `nx` and the corresponding values of `dx` and `dt`, a shorter time window is being examined overall.
# It's possible to do rigurous analysis of the stability of numerical schemes, in some cases.
#Watch Prof. Barba's presentation of this topic in **Video Lecture 9** on You Tube.


#%% [markdown]
## Chapter 2 Lab: Introduction to R (now in Python!)
# Please note that, the purpose of this file is *not* to demonstrate Python's basic functionalities (there are much more comprehensive guides, [like this](https://learnxinyminutes.com/docs/python3/)) but to mirror ISLR's lab in R as much as possible.
### Basic Commands
x = [1, 3, 2, 5]
print(x)
y = [1, 4, 3]
print(y)

#%% [markdown]
#### Get the length of a variable
print(len(x))
print(len(y))

#%% [markdown]
#### Element-wise addition of two lists
##### Pure Python
# Use [map](https://docs.python.org/2/library/functions.html#map) with [operator.add](https://docs.python.org/2/library/operator.html#operator.add) ([source](https://stackoverflow.com/a/18713494/4173146)):
from operator import add
x = [1, 6, 2]
print(list(map(add, x, y)))

#%% [markdown]
# or [zip](https://docs.python.org/2/library/functions.html#zip) with a list comprehension:
print([sum(i) for i in zip(x, y)])

#%% [markdown]
##### Using NumPy (will be faster than pure Python) ([source](https://stackoverflow.com/a/18713494/4173146)):
import numpy as np
x2 = np.array([1, 6, 2])
y2 = np.array([1, 4, 3])
print(x2 + y2)

#%% [markdown]
#### List all the variables
def printvars():
   tmp = globals().copy()
   [print(k,'  :  ',v,' type:' , type(v)) for k,v in tmp.items() if not k.startswith('_') and k!='tmp' and k!='In' and k!='Out' and not hasattr(v, '__call__')]
printvars()

#%% [markdown]
#### Clear a variable's content
x = None
print(x)
#%% [markdown]
#### Delete a variable (its reference)
del y
print(y)
#%% [markdown]
#### Delete all varialbes ([source](https://stackoverflow.com/a/53415612/4173146))
for name in dir():
    if not name.startswith('_'):
        del globals()[name]

for name in dir():
    if not name.startswith('_'):
        del locals()[name]
print(x2)
print(y2)
#%% [markdown]
# or simply restart the interpreter.

#%% [markdown]
#### Declare matrices ([source](https://stackoverflow.com/questions/6667201/how-to-define-a-two-dimensional-array-in-python))
##### Pure Python
rowCount = 4
colCount = 3
mat = [[0 for x in range(colCount)] for x in range(rowCount)]
print(mat)
#%% [markdown]
# or a shorter version:
mat = [[0] * colCount for i in range(rowCount)]
print(mat)

#%% [markdown]
# However, it is best to use numpy arrays to represent matrices.
import numpy
mat = numpy.zeros((rowCount, colCount))
print(mat)

#%% [markdown]
#### The sqaure root of each element of a vector or matrix (numpy array)
import numpy as np
mat = [[16] * colCount for i in range(rowCount)]
mat = np.asarray(mat)
print(np.sqrt(mat))

#%% [markdown]
#### Generate a vector of random normal variables
# Dimensions are provided as arguements to the numpy function.
#
# For random samples from a Normal distribution with mean *mu* and standard deviation *sigma*, use:
# `sigma * np.random.randn(...) + mu` according to the [documentation](https://docs.scipy.org/doc/numpy-1.16.0/reference/generated/numpy.random.randn.html#numpy.random.randn)
import numpy as np
x = np.random.randn(50)
y = x + ( 0.1 * np.random.randn(50) + 50 )
#%% [markdown]
# To compute the [Pearson correlation coefficient](https://en.wikipedia.org/wiki/Pearson_correlation_coefficient), or simply correlation, between the two vectors:
print(np.corrcoef(x, y))

#%% [markdown]
# To set the seed for random number generation, in Python:
import random
random.seed(0)
#%% [markdown]
# Or in numpy:
np.random.seed(0)
x = np.random.randn(50)
y = x + ( 0.1 * np.random.randn(50) + 50 )

#%% [markdown]
#### To compute the mean, variance, and standard deviation of a vector of numbers:
print(np.mean(x))
print(np.var(x))
print(np.std(x))
print(np.mean(y))
print(np.var(y))
print(np.std(y))

#%% [markdown]
### Graphics
#### Using matplotlib
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(0)
x = np.random.randn(100)
y = np.random.randn(100)

fig, ax = plt.subplots()
ax.scatter(x, y)
plt.show()

fig, ax = plt.subplots()
ax.set_xlabel(r'this is the x-axis')
ax.set_ylabel(r'this is the y-axis')
ax.set_title('Plot of X vs Y')
ax.grid(True)
fig.tight_layout()
ax.scatter(x, y)
#%% [markdown]
##### Save the plot to a file
fig.savefig('sample.png')
# remove the whitespace around the image
fig.savefig('sample.pdf', bbox_inches='tight')

#%% [markdown]
#### Using Seaborn
import seaborn as sns
fig, ax = plt.subplots()
ax.set_xlabel(r'this is the x-axis')
ax.set_ylabel(r'this is the y-axis')
ax.set_title('Plot of X vs Y')
ax.grid(True)
fig.tight_layout()
sns.scatterplot(x, y)

#%% [markdown]
#### Using Plotly
import plotly.graph_objects as go
fig = go.Figure(data=go.Scatter(x=x, y=y, mode='markers'))
fig.show()

#%% [markdown]
#### Using Bokeh
from bokeh.plotting import figure as bkfig, show as bkshow
fig = bkfig(title="Plot of X vs Y", tools='pan,wheel_zoom,box_zoom,reset,hover,crosshair', active_inspect='hover')
fig.circle(x=x, y=y)
fig.xaxis.axis_label = 'this is the x-axis'
fig.yaxis.axis_label = 'this is the y-axis'
bkshow(fig)

#%% [markdown]
#### Generating a sequence or range of numbers
# In pure Python, use `range(lower, upper, step)`:
# The following will generate a sequence of integers 1,...,10
x = range(1, 11)
for i in x:
        print(i)
#%% [markdown]
# To use non-decimal steps, or specify the number of elements to return, use the `linspace()` function in NumPy:
x = np.linspace(1, 10, 20)
print(x)
# Note that the PI constant in all of Python's math module, NumPy, and SciPy are the same.
x = np.linspace(-np.pi, np.pi, 50)
print(x)

#%% [markdown]
#### Contour plotting
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.pyplot as plt

x = np.linspace(-np.pi, np.pi, 50)
y = x
X, Y = np.meshgrid(x, y)
# [source](https://stackoverflow.com/a/45496154/4173146)
Z = np.cos(y) / (1 + x[:, None]**2)

fig, ax = plt.subplots()
CS = ax.contour(X, Y, Z)
ax.clabel(CS, inline=1, fontsize=4)

CS = ax.contour(X, Y, Z, 45)
ax.clabel(CS, inline=1, fontsize=4)

Z2 = ( Z - Z.T ) / 2
fig, ax = plt.subplots()
CS = ax.contour(X, Y, Z2, 15)
ax.clabel(CS, inline=1, fontsize=4)

#%% [markdown]
#### Filled contour (heatmap)
# Note: run the previous cell first.
fig, ax = plt.subplots(constrained_layout=True)
CS = ax.contourf(X, Y, Z2, 15, cmap=plt.cm.plasma)
cbar = fig.colorbar(CS)

#%% [markdown]
#### 3D contour plots
# 3D wireframe plot
from mpl_toolkits.mplot3d import axes3d
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(X, Y, Z2)
plt.show()

#%% [markdown]
# 3D surface (color map)
from matplotlib.ticker import LinearLocator, FormatStrFormatter
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z2, cmap=cm.terrain)
fig.colorbar(surf)
plt.show()

#%% [markdown]
# Project filled contour 'profiles' onto the 'walls' of the graph
from matplotlib.ticker import LinearLocator, FormatStrFormatter
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z2, cmap=cm.terrain, alpha=0.8)
fig.colorbar(surf)
# Plot projections of the contours for each dimension.  By choosing offsets
# that match the appropriate axes limits, the projected contours will sit on
# the 'walls' of the graph (from the [matplotlib documentation](https://matplotlib.org/3.1.1/gallery/mplot3d/contourf3d_2.html#sphx-glr-gallery-mplot3d-contourf3d-2-py))
cset = ax.contourf(X, Y, Z2, zdir='z', offset=ax.get_zlim()[0], cmap=cm.plasma, alpha=0.8)
# cset = ax.contourf(X, Y, Z2, zdir='x', offset=ax.get_xlim()[0], cmap=cm.plasma, alpha=0.8)
# cset = ax.contourf(X, Y, Z2, zdir='y', offset=ax.get_ylim()[1], cmap=cm.plasma, alpha=0.8)
plt.show()

#%% [markdown]
# Vary the viewing angle
for elev in [20, 70, 40]:
        fig = plt.figure()
        ax = fig.gca(projection='3d')
        surf = ax.plot_surface(X, Y, Z2, cmap=cm.terrain, alpha=0.8)
        fig.colorbar(surf)
        cset = ax.contourf(X, Y, Z2, zdir='z', offset=ax.get_zlim()[0], cmap=cm.plasma, alpha=0.8)
        ax.azim = 30
        ax.elev = elev

        plt.show()

#%% [markdown]
### Indexing Data
import numpy as np
A = np.array(np.arange(1, 17, 1)).reshape(4, 4).T
print(A)
# Python's arrays are zero-indexed
print(A[1, 2])
#%% [markdown]
#### Select multiple rows and columns
print(np.array([A[0,1], A[0,3], A[2,1], A[2,3]]).reshape(2,2))
print(A[:3, 1:4])
print(A[:2, :])
print(A[:, :2])
print(A[0])
#%% [markdown]
#### Keep all rows or columns except those indicated in the index
# To exclude whole rows or columns, use a boolean mask.
mask = np.ones(len(A), dtype=bool)
# Set the excluded indices to False
mask[[0,2]] = False
# Use the boolean/logical mask to index into the array
print(A[mask,...])
#%% [markdown]
# Get the dimensions of the array (matrix)
print(A.shape)

#%% [markdown]
### Loading Data
# We will use pandas to load and read the data
import pandas as pd
# Edit the path on your platform to correctly point to the file
df = pd.read_csv('Datasets\Auto.csv')
df.head()

#%% [markdown]
# Pass `'?'` as a NaN string
df = pd.read_csv('Datasets\Auto.csv', na_values='?')
df.head()
print(df.shape)
print(df.iloc[10:20,:])
# Find and display only rows with NaN's
df1 = df[df.isna().any(axis=1)]
df1.head()
print(df1.shape)

#%% [markdown]
# Drop the NaN values
df = df.dropna()
print(df.shape)

#%% [markdown]
# Get a list of column names in the DataFrame
print(list(df))

#%% [markdown]
### Additional Graphical and Numerical Summaries
# Scatterplots of the quantitative variables
df.plot.scatter(x='cylinders', y='mpg')

#%% [markdown]
# Boxplots, which are more suitable if the variable on the x-axis is categorical.
df_cat = df.pivot(columns='cylinders', values='mpg')
df_cat.plot.box()
df_cat.plot.box(vert=False)

#%% [markdown]
# Plot a histogram
df.hist(column='mpg')
df.hist(column='mpg', bins=15)

#%% [markdown]
# Plot a scatterplot matrix
from pandas.plotting import scatter_matrix
scatter_matrix(df[['mpg', 'displacement', 'horsepower', 'weight', 'acceleration']], diagonal='kde')

#%% [markdown]
# Plot the scatterplot, with custom variable values on mouse hover (using Plotly)
# Marker color and size can also be set to variables.
import plotly.graph_objects as go
fig = go.Figure(data=go.Scatter(x=df['horsepower'], y=df['mpg'], mode='markers', text=df['name']))
fig.update_layout(xaxis=go.layout.XAxis(title=go.layout.xaxis.Title(text='Horsepower')), yaxis=go.layout.YAxis(title=go.layout.yaxis.Title(text='MPG')))
fig.show()

#%% [markdown]
#### Descriptive statistics
# Include all variables
df.describe()

#%% [markdown]
# Include only a subset of variables
df['mpg'].describe()

#%% [markdown]
#### Serialization - save or load the current session
import dill
filename = 'globalsave.pkl'
dill.dump_session(filename)

# and to load the session again:
dill.load_session(filename)


###########################################################################################################
# The Central Limit Theorem states that THE SAMPLING DISTRIBUTION OF THE
# SAMPLE MEANS IS DISTRIBUTED LIKE IF IT WERE A NORMAL DISTRIBUTION.
#
# To prove this visually, let's draw a generic distribution (i.e. exponential)
# and let's sample out of it means of a subsample. Once done, let's plot an
# histogram to see how the sample mean distribution is...
###########################################################################################################

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

def sample_mean(x:list=None):

	return sum(x) / len(x)

# 1. Let's define a probability function that we assume we do not know as a population probability function...
def exponential(x=0, lamb=0, vector:list=None):

	if vector is not None:
		return [ exponential(x, lamb, vector=None) for x in vector ]
	else:
		return lamb * np.exp( -lamb * x )

# 2. Let's get out from the exponential distribution all the mean we may need...
mother_dist = exponential( lamb=0.1, vector = [ x  for x in range(0, 1000) ] )
sample_size = 450
extractions = 7500

sample_mean_extractions = [ sample_mean( random.sample(mother_dist, sample_size) ) for x in range(extractions) ]

# 3. Let's plot the sampling distribution of the sample mean to see how it goes !
plt.hist( sample_mean_extractions, bins = 50, edgecolor='black' )
plt.vlines( sample_mean(sample_mean_extractions), ymin=0, ymax=450, color='red' )
plt.show()

# Well, how does it look like the sampling distribution of the sample mean ?! I hope a "bell" rings inside you !
# Try to increase the "extractions" parameters to see how it goes...


# importing Python library
import numpy as np

# define Unit Step Function
def unitStep(v):
    if v >= 0:
        return 1
    else:
        return 0

# design Perceptron Model
def perceptronModel(x, w, b):
    v = np.dot(w, x) + b
    y = unitStep(v)
    return y

# NOT Logic Function
# wNOT = -1, bNOT = 0.5
def NOT_logicFunction(x):
    wNOT = -1
    bNOT = 0.5
    return perceptronModel(x, wNOT, bNOT)

# OR Logic Function
# w1 = 1, w2 = 1, bOR = -0.5
def OR_logicFunction(x):
    w = np.array([1, 1])
    bOR = -0.5
    return perceptronModel(x, w, bOR)

# NOR Logic Function
# with OR and NOT
# function calls in sequence
def NOR_logicFunction(x):
    output_OR = OR_logicFunction(x)
    output_NOT = NOT_logicFunction(output_OR)
    return output_NOT

# testing the Perceptron Model
test1 = np.array([0, 1])
test2 = np.array([1, 1])
test3 = np.array([0, 0])
test4 = np.array([1, 0])

print("NOR({}, {}) = {}".format(0, 1, NOR_logicFunction(test1)))
print("NOR({}, {}) = {}".format(1, 1, NOR_logicFunction(test2)))
print("NOR({}, {}) = {}".format(0, 0, NOR_logicFunction(test3)))
print("NOR({}, {}) = {}".format(1, 0, NOR_logicFunction(test4)))

'''
OUTPUT
NOR(0, 1) = 0
NOR(1, 1) = 0
NOR(0, 0) = 1
NOR(1, 0) = 0
'''

ml/Perceptrons/Perceptron as NOR operator.py


# p(x) = L0(x)f(x0)+...Ln(x)f(xn)
import sympy

x = sympy.Symbol('x')

xn =[2,2.1,2.2,2.3,2.4,2.5]
fxn =[-11.8646,-12.4775,-13.0891,-13.6997,-14.3092,-14.9179]
functions = []
def lagrange():
    for i in range(0,len(xn)):
        numerator = 1
        denominator = 1
        for j in range(0,len(xn)):
            if( i != j):

                numerator = numerator * (x-xn[j])
                denominator = denominator * (xn[i]-xn[j])
        functions.append(numerator/denominator)

lagrange()
polinomyal = 0
for i in range(0,len(functions)):
    polinomyal = polinomyal + fxn[i]*functions[i]
print(polinomyal)

d = sympy.simplify(polinomyal)
#f = d.evalf().subs({x:2.5})
#print()
print("simplify",d)


import numpy as np

def remove_dependent_variables(x, tol = np.finfo(np.float).eps):
    """
    Find independent columns using QR decomposition. The returned solution might
    not unique. There might be other subset of independent columns. Strict
    condition number of rows (m) > number of columns (n)
    :param x: The input numpy array
    :param tol: Tolerance, variables less than tol are removed
    :return: The linearly independent subset of variables
    """
    r = np.linalg.matrix_rank(x)
    n = x.shape[1]
    assert(r is not n), 'Matrix is already linearly independent'
    q, r = np.linalg.qr(x)
    ind  = np.where(np.abs(r.diagonal()) > tol)[0]
    return(ind, x[:, ind])

if __name__ == '__main__':
    """
    Simple use case
    """
    print('Define Matrix')

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

    print(A)

    print(' Output Matrix ')

    i, Y = remove_dependent_variables(A)

    print(Y)


#!/usr/bin/env python

"""This program finds the steady state solution of the geostrophic
adjustment the equations are: (i) the geostrophic balance, and (ii)
the conservation of potential vorticity. In cartesian geometry, these
two equations are

(i)    -v = dh/dx
(ii)   dv/dx - h = -h_0

with v the velocity and h the elevation of the interface

the program returns the solution vector (v,h) and plots the initial
condition (h_0 dashed) and final state (h)

v and h are discretized on a C-grid where the velocity is at the face
of the cell and the height of the interface is at the center of the
cell.

"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import fsolve

plt.ion()

flag_geom = 1 # geometry: 0: cartesian, 1: cylindrical coordinates

# both rmax and r0 are non dimensional variables
# the length unit is the deformation radius
rmax = 20  # tank radius
r0 = 10.0   #inner cylinder radius

Nx = 100     # number of points
si_r = 2*Nx + 1

rr = np.linspace(0,rmax,si_r)
dr = rr[1] - rr[0]

rr2 = 0.5*(rr[1:] + rr[:-1])

# initial condition
h0 = np.zeros(si_r+1)
h0[1:-1] = np.where(rr2<r0,-1,0)
h0[0] = h0[1]
h0[-1] = h0[-2]
v0 = np.zeros(si_r)

xx = np.concatenate((h0,v0))

def f(x,si_r,rr,dr,h0,flag_geom):
  h = x[:si_r+1]
  v = x[si_r+1:]

  fout = np.zeros(2*si_r+1)

  dhdr = (h[1:] - h[:-1])/dr
  dhdr[0] = 0.
  dhdr[-1] = 0.

  if flag_geom == 0:# cartesian
    fout[1:si_r] = (v[1:] - v[:-1])/(dr) - h[1:-1] + h0[1:-1]
    fout[si_r+1:] = -v + dhdr
  if flag_geom == 1:# cylindrical
    fout[1:si_r] = (rr[1:]*v[1:] - rr[:-1]*v[:-1])/(dr*0.5*(rr[1:]+rr[:-1])) - h[1:-1] + h0[1:-1]
    fout[si_r+1:] = -v -v**2/rr + dhdr
    fout[si_r+1] = 0.0

  return fout

sol = fsolve(f,xx,(si_r,rr,dr,h0,flag_geom))
hf = sol[1:si_r]
vf = sol[si_r+1:]

plt.figure()
plt.plot(rr2,hf,'k',label='h',linewidth=1)
plt.plot(rr2,h0[1:-1],'k--',linewidth=1)
#plt.plot(rr,vf,'r',label='v',linewidth=1)
plt.xlabel('r/Rd')
plt.ylabel('h/h0')
plt.legend()


import numpy as np
from scipy.integrate import odeint

def decay(y, t=0, l=1):
    """Linear decay model
    """
    dydt = -l * y
    return dydt

def logistic(y, t=0, r=1, k=1):
    """Logistic growth model
    """
    dydt = r*y*(1 - y/k)
    return dydt

def ass(y, t=0):
    """Basic model with two alternative stable states
    """
    dydt = -y**3 + y
    return dydt

def lotkavolterra(y, t=0, a=1, b=1, c=1, d=1):
    """Lotka-Volterra predator prey model
    """
    prey, pred = y
    dydt = [a * prey - b * pred * prey,
            c * pred * prey - d * pred]
    return dydt

def duffing(state, t, g=0.2, w=1.2, a=-1, b=1, d=0.3):
    """ Duffing oscillator
    Ref: https://en.wikipedia.org/wiki/Duffing_equation
    """
    x, v = state
    dydt = [v,
            -a*x - b*x**3 - d*v + g*np.cos(w*t)]
    return dydt

def strogatz(state, t=0, w=(2,1), k=(2,1)):
    """ Strogatz coupled oscillators model
    """
    if callable(w) & callable(k):
        w = w(t)
        k = k(t)

    th1, th2 = state
    dydt = [w[0] + k[0]*np.sin(th2 - th1),
            w[1] + k[1]*np.sin(th1 - th2)]

    return dydt

def dphilrob(state, t=0, tmax='default', Qmax=100*3600, theta=10, sigma=3, vmaSa=1, vvm=1.9/3600, vmv=1.9/3600, vvc=6.3, vvh=0.19, Xi=10.8, mu=1e-3, taum=10/3600, tauv=10/3600, w=2*np.pi/24, alpha=0.0):

    # Define auxiliary functions

    ## Saturation function
    S = lambda V : Qmax/(1 + np.exp((theta-V)/sigma))

    ## External forcing
    if tmax == 'default':
        C = lambda t : 0.5*(1 + np.cos(w*(t - alpha)))
    else:
        C = lambda t : (1 - t / tmax)*0.5*(1 + np.cos(w*(t - alpha)))

    Vv, Vm, H = state
    dydt =[(             -vvm*S(Vm) + vvh*H - vvc*C(t)         - Vv)/tauv, # Ventro-lateral preoptic area activity
           (-vmv*S(Vv)                     + vmaSa             - Vm)/taum, # Mono-aminergic group activity
           (              mu*S(Vm)                             -  H)/Xi] # Homeostatic pressure

    return dydt

def lorenz(state, t=0, a=10, b=28, c=8/3):
    x, y, z = state
    dydt = [a*(y - x),
            x*(b - z) - y,
            x*y - c*z]

    return dydt

def competition(y, t=0, r=1, a=1):
    """ Basic competition model
    """
    ry = np.multiply(r, y)
    ay = np.dot(a, y)
    dydt = np.multiply(ry, (1-ay))

    return dydt

def rosmac(y, t=0, r0=0.5, k=10, g0=0.4, h=2, l=0.15, e=0.6):
    """ Rosenzweig-MacArthur predator prey model
    """
    prey, cons = y

    def r(x):
        """ Growth rate """
        return r0*(1 - x/k)

    def g(x):
        """ Grazing rate """
        return g0/(x + h)

    dydt = [r(prey)*prey -g(prey)*prey*cons,
            -l*cons + e*g(prey)*prey*cons]
    return dydt

def hopf(state, t=0, a=1, b=1, l=-1):
    """Normal form for the Hopf bifurcation
    """
    from phdtools.dyntools import polarToCartesian

    x, y = state

    def hopf_pol(state, t=t, a=a, b=b, l=l):
        """Normal form for the Hopf bifurcation in polar coordinates
        """
        r, th = state
        drdt = [r * (l + a * r**2),
                1 + b * r**2]
        return drdt

    hopf_cart = polarToCartesian(hopf_pol)
    return hopf_cart(state)

def oscgen(state, t=0, amp=1, w=2*np.pi, g=1):
    """Oscillations generator
    """
    from phdtools.dyntools import polarToCartesian

    x, y = state

    def oscgen_pol(state, t=t, amp=amp, w=w, g=g):
        """Oscillations generator in polar coordinates
        """
        r, th = state
        drdt = [g * (amp - r),
                w]
        return drdt

    oscgen_cart = polarToCartesian(oscgen_pol)
    return oscgen_cart(state)

def reset_up(df, th_data, y0, ts, tinit):

    nsteps = len(ts)
    ys = np.zeros(nsteps)
    ys[0] = y0
    for i in range(1, nsteps):
        if ts[i] > tinit:
            if ys[i-1] < th_data[i-1]:
                ys[i] = ys[i-1] + df(ys[i-1], ts[i-1])*(ts[i] - ts[i-1])
            else:
                ys[i] = 0.0
        else:
            ys[i] = ys[i-1]

    return ys


#python3 Steven 11/15/2020, Auckland,NZ
#Loss function
#Reference: https://en.wikipedia.org/wiki/Loss_function
#https://papers.nips.cc/paper/2008/file/f5deaeeae1538fb6c45901d524ee2f98-Paper.pdf
import numpy as np

def Least_squares(x): #LS
    return (1-x)**2

def Modified_LS(x):
    def loss(x):
        return (np.max(1-x, 0))**2
    return list(map(loss, list(x)))

def SVM_Loss(x): #Hinge loss, SVM using
    def loss(x):
        return np.max(1-x, 0)
    return list(map(loss, list(x)))

def Boosting_Loss(x):
    return np.exp(-1*x)

def LogisticRegression(x):
    return np.log(1+np.exp(-1*x))

def Savage_loss(x):
    return 1/(1+np.exp(2*x))**2

def zero_one(x):
    y = np.zeros_like(x)
    l = np.where(x < 0)
    if len(l) != 0:
        y[l[0]]=1
    return y

#Cross-Entropy loss L = -(y*log(y') + (1-y)*log(1-y'))
def crossEntropy_GT01(y, yPred): # 0/1 classification, y:Ground truth: 0 or 1, yPred:0~1
    if y == 0:
        return -1*np.log(1-yPred)
    return -1*np.log(yPred) #y=1

#Cross-Entropy loss L = log(1 + e^(-y*y')) , activefun=sigmoid()
def crossEntropy_GT02(y, yPred): # +1/1 classification, y:Ground truth:-1 or +1, yPred:-1~1
    if y == 1:
        return np.log(1 + np.exp(-1*yPred))
    return np.log(1 + np.exp(yPred)) #y=-1

#Cross-Entropy(CE) Loss: CE(p) = -log(p), when y=1
#wighted Cross-Entropy, like Focal loss(FC), FC(p) = -(1-p)^gamma*log(p)
# FC == CE,when gamma == 0
def FocalLosss(p, gamma=0):
    return -np.power(1-p, gamma)*np.log(p) #return -gamma*np.log(p)

#CE(p) = -log(1-p), when y=0
#FC(p) = -(p)^gamma*log(1-p)
def FocalLosss1(p, gamma=0):
    return -np.power(p, gamma)*np.log(1-p) #return -gamma*np.log(p)

def TripletLoss(x, m=0.2):#https://en.wikipedia.org/wiki/Triplet_loss
    #max(t_p - t_n + m, 0)
    def loss(t):
        return max(t+m, 0)
    return list(map(loss, list(x)))

def ContrastiveLoss(x, m=0.2):
    #max(t_p - t_n + m, 0)
    def loss(t):
        return max(m-t, 0)
    return list(map(loss, list(x)))

def BinomialDevianceLoss(x):
    return np.log(1+np.exp(-2*x))


# Computação vetorizada com *numpy*

## Visão geral

- *Computação vetorizada* relaciona-se à execução de operações que podem ser feitas diretamente sobre *arrays*.

- *Arrays* multidimensionais: vetores (1D), matrizes (2D) e tensores (3D ou mais).

- O *numpy* é a biblioteca Python para computação vetorizada com *arrays* multidimensionais.

- Confere "superpoderes" às listas e eficiência a cálculos numéricos gerais.

- Com *numpy*, podemos ler e escrever arquivos, resolver sistemas lineares e realizar muito mais.

## Um conselho a ser seguido

> Vetorize seus cálculos numéricos evitando laços, iterações e repetições o máximo possível!

## Motivação

Este exemplo compara a eficiência de operações feitas com listas comuns e com *numpy*.

import numpy as np # alias np

L = range(500)
%timeit -n 10 [i**2 for i in L] # executa o laço 10 vezes

a = np.arange(500)
%timeit -n 10 a**2 # eleva ao quadrado diretamente 10 vezes

- 1 µs = $10^{-6}$ segundo
- 1 ns = $10^{-9}$ segundo

## Criação de arrays unidimensionais (1D)

a = [1,2,3]
np.array(a) # a partir de lista

np.array([1,2,3]) # diretamente

np.array([2]*5)

## Criação de arrays bidimensionais (2D)

A = [ [1,2], [0,2] ] # lista de listas
np.array(A) # matrix 2 x 2

np.array([ [1,2], [0,2] ]) # diretamente

A2 = [[1,2,3],[4,3,2]] # cada lista é uma linha da matriz
np.array(A2) # matriz 2 x 3

np.array([1,1],[0,1]) # colchetes externos são obrigatórios!

### Dimensão, formato e comprimento

x = np.array(a)
print(a)
np.ndim(x) # aplica a função ndim

x.ndim # como método

np.shape(x) # formato 1 x 3

x.shape # sem parênteses

len(x) # comprimento

A = [[1,2,3],[4,5,6]]
X = np.array(A)
np.ndim(X) # array bidimensional
X

np.shape(X) # 2 x 2

len(X) # apenas um comprimento. Qual?

X2 = np.array(A2)
len(X2) # apenas da primeira dimensão. LINHAS

X2.shape

## Funções para criação de arrays

### `arange`

**Exemplo:** crie um array de números ímpares positivos menores do que 36.

np.arange(1,36,2) # start,stop,step

**Exemplo:** crie um array de números pares positivos menores ou iguais a 62.

np.arange(0,63,2)

**Exemplo:** calcule o valor de $f(x) = x^3 + 2x$ para $x$ elementos dos arrays anteriores.

imp, par = np.arange(1,36,2), np.arange(0,63,2)
f = lambda x: x**3 + 2*x
fi, fp = f(imp), f(par)
print(fi)
print(fp)

### `linspace`

**Exemplo:** crie um array igualmente espaçado de elementos em [0,1] com 11 elementos.

np.linspace(0,1,num=11)

np.linspace(0,1,11) # 'num' pode ser omitido

x = np.linspace(0,1,10,endpoint=False) # exclui último
x

y = np.arange(0,1,0.1) # equivalente
y

x == y # comparação é elemento a elemento

x == -y # apenas 0 é True

x[1:] == y[1:] # indexação

### `all` e `any`

np.all( x == y ) # verifica se todos são 'True'

np.any( x == -y ) # verifica se pelo menos um é 'True'

### `random`

**Exemplo**: crie um *array* 1D com 5 números aleatórios entre [0,1].

r = np.random.rand(5)
r

**Exemplo**: crie um *array* 1D com 50 números inteiros aleatórios entre [0,7].

r2 = np.random.randint(0,7+1,50) # menor, maior é 8 (exclusive), tamanho
r2

**Exemplo**: crie uma matriz m x n com números inteiros aleatórios entre inteiros [l,h].

def gera_matriz(m,n,l,h):
    return np.random.randint(l,h,(m,n)) # tupla (m,n)

gera_matriz(2,2,0,2)

gera_matriz(3,2,0,4)

gera_matriz(4,4,-2,7)

### `ones`

Criando arrays unitários.

np.ones(4)

np.ones((6,6)) # tupla necessária para linhas e colunas

### `eye`

Criando arrays 2D identidade. 1 na diagonal e 0 nas demais.

np.eye(15) # matriz identidade 3 x 3

### `zeros`

Arrays nulos.

np.zeros(8)

np.zeros((3,3)) # 2 x 4

### `full`

Arrays de valor constante.

np.full(3,0) # 1 x 3 com constante 0

np.full(shape=(3,),fill_value=0)

F1 = np.full(shape=(2,2),fill_value=1) # 2 x 2 com 1
F1

F1 == np.ones(2) # mesmo resultado que ones

Outras maneiras:

F2 = 3*np.ones((4,4))
F2

## Especificando tipos de dados

F2

F3 = np.full((4,4),3)
F3

F2 == F3 # valores iguais

F2.dtype == F3.dtype # tipos diferentes

F2.dtype

F3.dtype

Especificamos o tipo de dados com `dtype`

np.ones((4,2),dtype=bool) # matriz de booleanos

np.ones((4,2),dtype=str) # matriz de strings; 'U1' diz que há no máximo 1 caracter

S = np.array(['dias','mes','ano'])
S.dtype # 4 é o no. máximo de caracteres nas strings

## Indexação e fatiamento

Funcionam de maneira similar ao uso com listas

I = np.linspace(0,20,11)
I

I[3],I[2:4],I[5:8],I[-4:-1]

I[::-1] # invertendo o array

I2 = np.array([I,2*I,3*I,4*I])
I2

Em arrays bidimensionais, a indexação é feita por meio de uma tupla. Porém, a explicitação dos parênteses é desnecessária.

I2[(2,3)] # 3a. linha; 4a. coluna

I2[2,3]

I2[0,:] # 1a. linha

I2[1,:] # 2a. linha

I2[-1,:] # última linha

I2[:,0] # 1a. coluna

I2[:,1] # 2a. coluna

I2[:,8] # 9a. coluna

I2[:,2:4] # 3a. - 4a. coluna

I2[1:3,6:10] # submatriz: linhas 2 - 3; 7-10

### Alteração de valores

Os arrays são mutáveis por indexação.

A3 = np.random.rand(4,4)
A3

A3[0:4,0] = -1
A3

A3[:,-1] = -1
A3

A3[1:3,1:3] = 0
A3

Podemos alterar valores com arrays.

A3[1,:] = -2*np.ones(4)
A3

A indexação pode usar um comprimento de passo (*step*).

A3[0:4:3,1:3] = np.full((1,2),8) # na indexação esquerda, 1a. linha : 4a. linha : step de 3
A3

### `newaxis`

`newaxis` é uma instância do `numpy` que permite aumentar de 1 a dimensão de um array existente.

**Exemplo:** como inserir a diagonal de uma matriz em uma segunda matriz como uma coluna adicional?

Criamos duas matrizes aleatórias.

# matriz 4 x 4 de inteiros aleatórios entre 0 e 9
B1 = np.random.randint(0,10,(4,4))
B1

# matriz 4 x 4 de inteiros aleatórios entre -10 e 9
B2 = np.random.randint(-10,10,(4,4))
B2

Extraímos a diagonal da primeira.

# diagonal de B1
db1 = np.diag(B1)
db1

Notemos agora que as dimensões são diferentes.

print(B2.ndim)
print(db1.ndim)

Para podermos aglutinar a diagonal como uma nova coluna na primeira matriz, primeiro temos que transformar o array unidimensional para uma matriz.

db1 = db1[:,np.newaxis]
print(db1.ndim) # agora o array é bidimensional
db1

`newaxis` é um "eixo imaginário" incluído *inplace*, mas que altera dinamicamente o array. No caso acima, o array tornou-se em uma coluna.

Agora, podemos "colar" um array 2D com outro por uma concatenação.

### `concatenate`

`concatenate` é usado para concatenar *arrays*. A concatenação requer uma tupla contendo os *arrays* a concatenar e o eixo de referência.

B3 = np.concatenate((B2,db1), axis=1)
B3

- `axis=1` indica concatenação ao longo da coluna
- Inserimos a segunda diagonal como uma coluna adicional na segunda matriz
- Isto foi possível porque ambas as matrizes eram de mesmo formato
- É necessário observar o formato dos *arrays*

#### `axis`

- Nos arrays multidimensionais do Python, `axis` é usado para indicar a "direção" dos dados.

Em arrays bidimensionais:
- `axis=0` refere-se à direção de cima para baixo (ao longo das linhas)
- `axis=1` refere-se à direção da esquerda para a direita (ao longo das colunas).

**Obs.:** note que a palavra `axis` ("eixo") deve ser usada, e não "axes" ("eixos").

Para aglutinar uma linha na matriz anterior, fazemos uma concatenação em linha.

# array de zeros com mesmo número de colunas de B3
db2 = np.zeros(np.shape(B3)[1])
db2

db2 = db2[np.newaxis,:] # cria o "eixo imaginário" na direção 0

B4 = np.concatenate((B3,db2),axis=0) # concatena ao longo das linhas
B4

## Indexação avançada

Podemos usar máscaras como artifícios para indexação avançada.

IA1 = np.arange(-10,11)
IA1

Vamos criar um *array* aleatório de True e False no mesmo formato que o *array* anterior.

mask1 = np.random.randint(0,2,np.shape(IA1),dtype=bool)
mask1

Esta *máscara booleana* pode ser aplicada no array para extrair apenas os elementos cujos índices são marcados como `True` pela máscara.

IA1[mask1]

Há maneiras mais diretas aplicáveis a filtragens. Para extrair os valores negativos do array:

IA1 < 0 # máscara booleana

IA1[IA1 < 0]

Para extrair os valores positivos do array:

IA1[IA1 > 0] # máscara booleana para positivos

Para extrair os valores no intervalo $]-2,5[$, fazemos:

IA1[(IA1 > -2) & (IA1 < 5)] # & é o operador booleano 'elemento a elemento'

Para extrair pares e ímpares, poderíamos fazer:

pares, impares = IA1[IA1 % 2 == 0] , IA1[IA1 % 2 == 1]
pares,impares

Podemos usar listas como máscaras:

alguns = pares[[0,2,3,5]] # acessa 1o., 3o. 4o. e 6o. elemento de 'pares'

impares[alguns] # estude este caso

- -10 é indexação reversa excededida. Retorna o 1o. elemento do array: -9.
- -6 acessa o 6o. elemento a partir da direita, que é -1.
- -4 acessa o 4o. elemento a partir da direita.
- 0 acessa o primeiro elemento que é -9.

## Operações elemento a elemento

- As operações aritméticas e de cálculo são feitas elemento a elemento nos *arrays*.
- Já fizemos isso, mas vejamos claramente com mais exemplos

a = np.array([1,2,3])
b = np.array([4,5,6])

# operações elemento a elemento
print(a + b)
print(a - b)
print(a * b)
print(a / b)
print(a ** b)

2*a + 4*b - 6*b**2 + 1.1/2*a

## Funções matemáticas

- O `numpy` possui a maioria dass funções de `math` e outras mais.
- As funções são diretamente aplicáveis aos *arrays*.
- Como era com listas? Tínhamos de iterar sobre elas...
- Com `numpy`, esse problema está resolvido: isto é computação vetorizada.

x = np.arange(10)
x

np.sqrt(x)

np.cos(x) + 2*np.sqrt(x)

y = np.sin(2*x)
z = np.exp(x + y)
y - z

### Problema resolvido (Laboratório Computacional 1C)

Observe a tabela a seguir, onde **DS (UA)** é a distância do referido planeta do até o Sol em unidades astronômicas (UA), **Tm (F)** sua temperatura superficial mínima em graus Farenheit e **TM (F)** sua temperatura superficial máxima em graus Farenheit.

| | DS (UA) | Tm (F) | TM (F) | DS (km) | TmM (C) |
|--|--|--|--|--|--|
Mercúrio | 0.39 | -275 | 840 | ? | ? |
Vênus | 0.723 | 870 | 870 | ? | ? |
Terra | 1.0 | -129 | 136 | ? | ? |
Marte | 1.524 | -195 | 70 | ? | ? |

- Escreva um código para converter a temperatura dos planetas de graus Farenheit (**F**) para Celsius (**C**).

- Escreva um código para converter unidades astronômicas em quilômetros.

- Imprima os valores que deveriam ser inseridos na coluna **DS (km)** horizontalmente usando `print`.

- Repita o item anterior para a coluna **TmM (C)**, que é a média aritmética entre **Tm** e **TM**.

*Observação:* use notação científica (exemplo: $4.2 \times 10^8$ pode ser escrito como `4.2e8` em Python).

#### Resolução

Há várias maneiras de resolver. Aqui apresentamos uma estratégia com `lambdas`.

- Montar os arrays dos dados numéricos.

DS = np.array([0.39,0.723,1.0,1.524])
Tm = np.array([-275,870,-129,-195])
TM = np.array([840,870,136,70])

- Fórmula e cálculo da conversão Farenheit para Celsius:

C = lambda F: 5/9*(F-32)
CTm = C(Tm)
CTM = C(TM)
print(CTm) # minimas em C
print(CTM) # maximas em C

- Fórmula e cálculo da conversão UA para km:

UA = lambda km: 1.496e+8*km
UADS = UA(DS)
print(UADS) # valores a inserir

- Cálculo da média

TmM = 0.5*(CTm + CTM)
print(TmM)

### `reshape` e `hstack`

- Montagem do array bidimensional com resultdos não requisitada.
- Mostramos uma maneira de fazer isto com `reshape` e `hstack
- `reshape` é uma função para reformatar os dados
- `hstack` é usada para "empilhar" arrays horizontalmente.

**Obs:** consulte também `vstack`.

#####  Nota:

- Todos os *arrays* são unidimensionais.
- Vamos torná-los bidimensionais com formato 4 x 1
- Depois, empilhá-los horizontalmente -> direção do eixo 1 (esquerda para direita).

todos = [DS,CTm,CTM,UADS,TmM] # lista com todos os arrays

for i,ar in enumerate(todos):
    todos[i] = np.reshape(ar, (4,1)) # reformata

final = np.hstack(todos) # empilha

Explicando o que fizemos:

- Colocamos todos os arrays em uma lista: neste ponto, nada novo.
- Iteramos sobre a lista, reformatamos um por um e reatribuímos na mesma lista como arrays bidimensionais

Para o segundo ponto, observe:

DS.shape # formato é 1 x 4 (unidimensional)

np.reshape(DS,(4,1)) # reformata

np.reshape(DS,(4,1)).shape # novo formato é 4 x 1

np.reshape(DS,(4,1)).ndim # o array agora é bidimensional

- Procedendo assim para todos, reformatamos e adicionamos cada um em uma lista.
- Se desejarmos, sobrescrevemos a lista ou não.
- Na resolução, escolhemos sobrescrever.
- Assim, suponha que a lista dos arrays reformatados seja:

L = [np.reshape(DS,(4,1)),np.reshape(TmM,(4,1))] # apenas DS e TmM
L

- Criamos o array final por empilhamento.

Note que:

- A lista `L` possui 2 arrays de formato 4 x 1.
- Para criar o array 4 x 2, faremos um empilhamento horizontal
- Isto é similar a uma concatenação na direção 1

Lh = np.hstack(L)
Lh

Agora podemos verificar que, de fato, o array está na forma como queremos.

Lh[:,0] # 1a. coluna idêntica à DS

Lh[:,0] == DS # teste

np.all( Lh[:,0] == DS ) # teste completo

Lh[:,1] # 2a. coluna idêntica a TmM

Lh[:,1] == TmM # teste

np.all( Lh[:,1] == TmM ) # teste completo

## *Broadcasting*

*Broadcasting* é a capacidade que o *numpy* oferece para realizarmos operações em arrays com diferentes dimensões.

**Obs:** para entender o *broadcasting*, veja o material.

### Regras do *broadcasting*

1. Se dois *arrays* tiverem dimensões diferentes, o formato do array com menor dimensão é preenchido por 1 do lado esquerdo.
2. Se o formato dos *arrays* não for igual em dimensão alguma, o array com tamanho igual a 1 é esticado nesta direção para ficar no mesmo tamanho correspondente do outro array.
3. Se em qualquer direção os tamanhos dos *arrays* forem diferentes e nenhum deles for igual a 1, então um erro é retornado.

#### Exemplo da Regra 1

A = np.array([[1, 2, 3],[4, 5, 6]]) # array 2D
b = np.array([10, 20, 30]) # array 1D
print(A.shape)
print(b.shape)

A + b

A soma pode ser realizada mesmo assim. O que ocorreu? Cada linha de `A` foi somada à única linha de `b`. O *broadcasting* amplia o array de menor dimensão automaticamente da seguinte forma:

Pela regra 1, o *array* `b` tem dimensão menor. Então, ele é preenchido de modo que:

```python
A.shape -> (2, 3)
b.shape -> (1, 3)
```

Pela regra 2, a primeira dimensão de `A` é 2 e a de `b` é 1. Então, a dimensão de `b` é "esticada", de modo que:

```python
A.shape -> (2, 3)
b.shape -> (2, 3)
```

A mesma operação poderia ter sido feita com:

A + np.array([b,b])

#### Exemplo da Regra 2

A = np.arange(3).reshape((3, 1))
b = np.arange(3)
print(A.shape)
print(b.shape)

A + b

Neste caso, ambos os arrays sofrem *broadcasting*. Ele ocorre da seguinte forma.

Como

```python
A.shape = (3, 1)
b.shape = (3,)
```
a regra 1 diz que `b` deve ser preenchido de modo que

```python
A.shape -> (3, 1)
b.shape -> (1, 3)
```
e, pela regra 2, cada uma das dimensões 1 deve ser alterada de modo que:

```python
A.shape -> (3, 3)
b.shape -> (3, 3)
```

Assim, o *broadcasting* é permitido.

#### Exemplo da Regra 3

A = np.ones((3, 2))
b = np.arange(3)
print(A.shape)
print(b.shape)

A + b

Neste exemplo, o *broadcasting* não é permitido. O caso é levemente diferente do primeiro exemplo em que `A` é transposta.

Temos que

```python
M.shape = (3, 2)
a.shape = (3,)
```

Pela regra 1, devemos ter

```python
M.shape -> (3, 2)
a.shape -> (1, 3)
```

e, pela regra 2, a primeira dimensão deve ser esticada para combinar-se com a de `A` enquanto a segunda não é alterada por não ser 1.

```python
M.shape -> (3, 2)
a.shape -> (3, 3)
```
Porém, o formato final de ambos não se combina. Sendo incompatíveis, o *broadcasting* falha.


import sys
import random
import numpy as np
import scipy as sc
import matplotlib as mlp
import matplotlib.pyplot as plt

from matplotlib import rc
from scipy import special

def cauchyPDF(x, x0 , gamma = 1):
    '''
    Returns the cauchy PDF
    @params:
        x (np.array): Points of interest
        x0 (float): Mean location of cauchy
        gamma (float): probably error
    '''
    return 1 / (np.pi*gamma*(1 + (x**2 -2*x*x0 + x0**2)/(gamma**2)))

def cauchy(n, x0 , gamma = 1):
    '''
    Randomly samples from a cauchy PDF
    @params:
        n (int): Number of samples desired
        x0 (float): Mean location of cauchy
        gamma (float): probably error
    '''
    Y = np.random.uniform(0,1,n)
    return gamma*np.tan(np.pi*(Y-0.5)) + x0

def gammaPDF(x, a , b = 1.0):
    '''
    Returns the gamma PDF
    @params:
        x (np.array): Points of interest
        a (float): hyper-parameter
        b (float): hyper-parameter
    '''
    #Note scipy.special.expit = np.exp but np.exp will over flow when x is negative
    #scipy.special.expit dances around that by computing 1/(1+exp(-x))
    return x**(a-1) * sc.special.expit(-x/b - np.log(sc.special.gamma(a)) + np.log(b**a))

if __name__ == '__main__':
    plt.close('all')
    mlp.rcParams['font.family'] = ['times new roman'] # default is sans-serif
    rc('text', usetex=True)

    #Set up subplots
    f, ax = plt.subplots(1, 2, figsize=(10, 5))
    f.suptitle('Homework 4 Problem 1', fontsize=14)

    #Parameters
    a = 10 # Must be greater than 1
    M = (np.pi*np.sqrt(2*a-1))/sc.special.gamma(a)*np.exp(-a+1)*(a-1)**(a-1)
    x =  np.linspace(0,30,100)

    gamma_PDF = gammaPDF(x, a)
    cauchy_PDF = M*cauchyPDF(x, a-1, np.sqrt(2*a-1))

    #Accept/Reject Monte carlo
    #Get random samples from our "simpler" cauchy distribution
    X = cauchy(5000, a-1, np.sqrt(2*a-1)).tolist()
    #Now accept/reject the sample points
    samples = [ x0 for x0 in X  if (random.random() < gammaPDF(x0, a)/(M*cauchyPDF(x0, a-1, np.sqrt(2*a-1))))]
    bins = np.linspace(0, 30, 30)

    #Plot profiles
    ax[0].plot(x, gamma_PDF, 'g', label=r'$Gamma(x|a,1)$')
    ax[0].plot(x, cauchy_PDF, 'r', label=r'$Cauchy(x|a-1,\sqrt{2a-1})$')
    ax[0].set_xlim((0,30))
    ax[0].set_ylim(ymin=0)
    ax[0].legend()
    ax[0].set_title('Profiles')

    #Plot histrogram
    ax[1].hist(np.array(samples), bins, color='blue', alpha = 0.5, normed=1, edgecolor = "black")
    ax[1].plot(x, gamma_PDF, 'g', label=r'$Gamma(x|a,1)$')
    ax[1].set_xlim((0,30))
    ax[1].set_ylim(ymin=0)
    ax[1].legend()
    ax[1].set_title('Accept/Reject')
    plt.show()


import numpy as np
from math import sqrt
from tabulate import tabulate

def print_matrix(Title, M):
    print(Title)
    for row in M:
        print([x for x in row])

def decompose_matrix(matrix):

    n = len(matrix)
    U = matrix.copy()
    L = np.eye(n, dtype=np.double) #Identity Matrix
    P = np.eye(n, dtype=np.double)
    for i in range(n):
        for k in range(i, n):
            if ~np.isclose(U[i, i], 0.0):
                break
            U[[k, k+1]] = U[[k+1, k]]
            P[[k, k+1]] = P[[k+1, k]]

        factor = U[i+1:, i] / U[i, i]
        L[i+1:, i] = factor
        U[i+1:] -= factor[:, np.newaxis] * U[i]

    return P, L, U

def forward_substitution(L, b):

    n = L.shape[0]
    y = np.zeros_like(b, dtype=np.double);

    y[0] = b[0] / L[0, 0]

    for i in range(1, n):
        y[i] = (b[i] - np.dot(L[i,:i], y[:i])) / L[i,i]
    return y

def back_substitution(U, y):
    n = U.shape[0]
    x = np.zeros_like(y, dtype=np.double);
    x[-1] = y[-1] / U[-1, -1]

    for i in range(n-2, -1, -1):
        x[i] = (y[i] - np.dot(U[i,i:], x[i:])) / U[i,i]
    return x

def plu_inverse(A):
    n = len(A)
    b = np.eye(n)
    Ainv = np.zeros((n, n))
    P, L, U = decompose_matrix(A)
    for i in range(n):
        y = forward_substitution(L, np.dot(P, b[i, :]))
        Ainv[:, i] = back_substitution(U, y)
    return Ainv

def find_condition_number(A):
    """Finds the condition number of the matrix A, given the inverse matrix"""

    n = len(A)

    max_in_rows = np.amax(np.abs(A), axis=1)
    max_in_normal = np.amax(A, axis=1)

    for i in range(n):          #Normalize matrix
        for j in range(n):
            if max_in_rows[i] == max_in_normal[i]:
                A[i][j] = A[i][j]/max_in_rows[i]
            else:
                A[i][j] = A[i][j]/-max_in_rows[i]

    max_in_rows =np.amax(np.abs(A).sum(axis=1)) #Find max in  rows with sum of abs
    A_inv = plu_inverse(np.array(A))

    max_in_inv_rows = np.amax(np.abs(A_inv).sum(axis=1))

    cond_A = max_in_inv_rows*max_in_rows
    print("Condition number of A: ", cond_A)

def transpose(L):
    """Transposes a lower triangular matrix L."""
    n = len(L)
    LT = [[0.0] * n for i in range(n)]
    for i in range(n):
        for j in range(n):
            LT[j][i] = L[i][j]
    return LT

def cholesky(A):
    '''
    Cholesky decomposition of a positive definite matrix A.
    Returns the lower triangular matrix L such that A = L*LT.
    '''

    n = len(A)

    L = [[0.0] * n for i in range(n)]

    for i in range(n):
        for k in range(i+1):
            tmp_sum = sum(L[i][j] * L[k][j] for j in range(k))

            if (i == k):
                L[i][k] = sqrt(A[i][i] - tmp_sum)
            else:
                L[i][k] = (1.0 / L[k][k] * (A[i][k] - tmp_sum))
    U = transpose(L)
    return L,U

if __name__ == "__main__":

    matrix = [[87.82557,0.0,-43.91278,3659.399,0.0,0.0],
              [0.0,813199.7,-3659.399,203299.9,0.0,0.0],
              [-43.91278,-3659.399,87.82557,0.0,-43.91278,3659.399],
              [3659.399,203299.9,0,813199.7,-3659.399,203299.9],
              [0.0,0.0,-43.91278,-3659.399,44.41278,-3659.399],
              [0.0,0.0,3659.399,203299.9,-3659.399,406599.8]]

    ## Question 1-A)
    #P,L,U = decompose_matrix(np.array(matrix))
    #print_matrix("L", L)
    #print_matrix("U", U)

    ## Question 1-B)
    #L,U = cholesky(matrix)
    #print_matrix("L", L)
    #print_matrix("U", U)

    ## Question 1-C)
    #A_Inv = plu_inverse(np.array(matrix))
    #print_matrix("A_Inv", A_Inv)

    ## Question 1-D)
    #find_condition_number(np.array(matrix))


import numpy as np

def activation_function(name, x, derivative=False):
    """
    Computes the activation function and its derivative.

    Parameters
    ----------
    name: str
        Activation function name.
          Options:
              identity
              sigmoid
              softmax
              tanh
              relu

    x: int/float/list/array
        Input.

    derivative: bool
        If true, returns the derivative of loss.
        Default: False

    Returns
    -------
    Numpy array or list
    """

    if name == "identity":
        if derivative:
            return np.ones_like(x)
        else:
            return x
    elif name == "sigmoid":
        if derivative:
            out = activation_function(name, x)
            return out * ( 1 - out )
        else:
            # Prevents overflow
            x_clipped = np.clip(x, -500, 500)
            return 1 / (1 + np.exp(-x_clipped))
    elif name == "softmax":
        if derivative:
            out = activation_function(name, x)
            return out * (1 - out)
        else:
            # Prevents overflow
            x_clipped = np.clip(x, -500, 500)
            e_x = np.exp(x_clipped - np.max(x_clipped))
            return e_x / np.sum(e_x, axis=1, keepdims=True)
    elif name == "tanh":
        if derivative:
            out = activation_function(name, x)
            return 1 - np.square(out)
        else:
            return 2 / (1 + np.exp(-2*x)) - 1
    elif name == "relu":
        if derivative:
            return (x > 0) * 1
        else:
            return np.maximum(0, x)

def loss_function(name, y, y_hat, derivative=False):
    """
    Computes the loss and its derivative

    Parameters
    ----------
    name: str
        Type of loss function.
        Options:
            mse ( Mean squared error )
            ce ( Cross entropy )

    y: list
        numpy array ( target )

    y_hat: list
        numpy array ( output )

    derivative: bool
        If True, returns the derivative of loss.
        Default: False

    Returns
    -------
    numpy array
    """

    # y - target, y_hat - output
    # Mean Squared Error
    if name == "mse":
        if derivative:
            return (y_hat - y)
        else:
            return np.mean((y - y_hat)**2)
    # Log-likelihood
    elif name == "ll":
        if derivative:
            return - (1 / y_hat)
        else:
            return -1 * np.log(y_hat)
    # y - target prob distro, y_hat - output prob distro
    # Cross Entropy
    elif name == "ce":
        if derivative:
            # if activation fn is sigmoid/softmax
            return (y_hat - y)
        else:
            # prevents overflow
            y_clipped = np.clip(y_hat, 1e-8, None)
            return np.sum(np.nan_to_num(-y*np.log(y_clipped)-(1-y)*
                                        np.log(1-y_clipped)))


from statistics import mean
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import style

style.use('fivethirtyeight')

xs = np.array([1,2,3,4,5,6], dtype = np.float64)
ys = np.array([5,4,6,5,6,7], dtype = np.float64)

def best_fit_slope_and_intercept(xs, ys):
	m = (  ((mean(xs)*mean(ys)) - mean(xs*ys)) /
		((mean(xs)*mean(xs)) - mean(xs*xs)) )

	b = ( mean(ys) -(m* mean(xs)) )
	return m, b

def squared_error(ys_orig, ys_line):
	return sum((ys_line - ys_orig)**2)

# r is co-efficient of determination

def coefficient_of_determination(ys_orig, ys_line):
	y_mean_line = [mean(ys_orig) for y in ys_orig]
	squared_error_regr = squared_error(ys_orig, ys_line)
	squared_error_y_mean = squared_error(ys_orig, y_mean_line)
	return 1 - (squared_error_regr/squared_error_y_mean)

m, b = best_fit_slope_and_intercept(xs, ys)

regression_line = [(m*x)+b for x in xs]

predict_x = 1.5
predict_y = (m*predict_x + b)

r_squared = coefficient_of_determination(ys, regression_line)
print(r_squared)

plt.scatter(xs, ys)
plt.scatter(predict_x, predict_y, color='g')
plt.plot(xs, regression_line)
plt.show()


import numpy as np

class bernoulli():
    def pmf(x,p):
        """
        probability mass function
        """
        f = p**x*(1-p)**(1-x)
        return f

    def mean(p):
        """
        expected value of bernoulli random variable
        """
        return p

    def var(p):
        """
        variance of bernoulli random variable
        """
        return p*(1-p)

    def std(p):
        """
        standart deviation of bernoulli random variable
        """
        return bernoulli.var(p)**(1/2)

    def rvs(p,size=1):
        """
        random variates
        """
        rvs = np.array([])
        for i in range(0,size):
            if np.random.rand() <= p:
                a=1
                rvs = np.append(rvs,a)
            else:
                a=0
                rvs = np.append(rvs,a)
        return rvs

p=0.2 # probability of having an accident

bernoulli.mean(p) # return -> 0.2
bernoulli.var(p) # return -> 0.16
bernoulli.std(p) # return -> 0.4

bernoulli.rvs(p,size=10)
#return-> array([0., 0., 0., 0., 1., 0., 1., 0., 0., 1.])


#!/usr/bin/env python

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

PATH = 'view/'
GRAPH = 'bisection'
MAX = 30
TOLERANCE = 0.00001

log = [] # a, b, c, fnc

def function ( x ):
    return ( x**3 - x - 2 )

def same_sign(product_a , product_b) :
    return ((product_a * product_b) > 0)

def bisection (fn, a, b, tol, nmax):
    ''' description
        is a root-finding method that applies to any continuous functions
            for which one knows two values with opposite signs
        The method consists of repeatedly bisecting the interval defined
            by these values and
            then selecting the subinterval in which the function changes sign,
            and therefore must contain a root.
    '''
    # Bolzano's Theorem: check the intermediate value of functions a and b
    if same_sign(fn(a), fn(b)):
        return "Error 32: Bolzano of Theorem"
        breakpoint

    # limit iterations to prevent infinit loop
    for n in range(nmax) :
        # midpoint
        c = ( (a+b) / 2)
        fn_c = fn(c)
        log.append([ a, b, c, fn_c ])
        if (fn_c) == 0 or ((b-a)/2) < tol :
            # soluction found
            return c
            breakpoint
        if same_sign(fn(a), fn_c) :
            a = c
        else :
            b = c
    return False

def graph(name) :
    len_log = len(log)
    x = np.arange(1,(len_log+1),1)
    y = np.zeros((len_log), dtype=float)

    for i in range(len_log) :
        y[i] = ( math.log2(abs( log[i][3] )) )

    plt.plot(x, y, label = "blue", color="#6776FE", marker=".")

    x[:] = (x[:]-1)*(-1)
    plt.plot(x, label = "blue", color="#FF5733")

    plt.title(GRAPH+' '+name)
    plt.xlabel("Iteration axis")
    plt.ylabel("ln(|x0 - xn|) axis")
    plt.savefig(PATH+GRAPH+name+'.pdf', dpi=300)

if __name__ == "__main__":
    ''' Tests
        * test_0 = fx = ( x**3 - x - 2 ), a = 1, b = 2, c = 2.23606
        * test_1 = fx = ( x**2 - 5 ), a = 0, b = 4, c = 1.521385
        * test_2 = fx = ( x**2 - 3 ), a = 0, b = 4, c = (3)^1/2
        * test_3 = fx = ( x**3 + x - 3), a = 0, b = 4, c = 1.2134170
        * http://www.mathcs.emory.edu/~cheung/Courses/170/Syllabus/07/bisection.html
   '''
    a = 1
    b = 2

    result = ( bisection(function, a, b, TOLERANCE, MAX) )
    print(result)
    result = str(int(result*10000))
    graph(result)


import numpy as np

def softmax(action_values, tau=1.0):
    """
    Args:
        action_values (Numpy array): A 2D array of shape (batch_size, num_actions).
                       The action-values computed by an action-value network.
        tau (float): The temperature parameter scalar.
    Returns:
        A 2D array of shape (batch_size, num_actions). Where each column is a probability distribution over
        the actions representing the policy.
    """

    # Compute the preferences by dividing the action-values by the temperature parameter tau
    preferences = action_values / tau
    # Compute the maximum preference across the actions
    max_preference = np.max(preferences, axis=1)

    # your code here

    # Reshape max_preference array which has shape [Batch,] to [Batch, 1]. This allows NumPy broadcasting
    # when subtracting the maximum preference from the preference of each action.
    reshaped_max_preference = max_preference.reshape((-1, 1))
    # print(reshaped_max_preference)

    # Compute the numerator, i.e., the exponential of the preference - the max preference.
    exp_preferences = np.exp(preferences - reshaped_max_preference)
    # print(exp_preferences)
    # Compute the denominator, i.e., the sum over the numerator along the actions axis.
    sum_of_exp_preferences = np.sum(exp_preferences, axis=1)
    # print(sum_of_exp_preferences)

    # your code here

    # Reshape sum_of_exp_preferences array which has shape [Batch,] to [Batch, 1] to  allow for NumPy broadcasting
    # when dividing the numerator by the denominator.
    reshaped_sum_of_exp_preferences = sum_of_exp_preferences.reshape((-1, 1))
    # print(reshaped_sum_of_exp_preferences)

    # Compute the action probabilities according to the equation in the previous cell.
    action_probs = exp_preferences / reshaped_sum_of_exp_preferences
    # print(action_probs)

    # your code here

    # squeeze() removes any singleton dimensions. It is used here because this function is used in the
    # agent policy when selecting an action (for which the batch dimension is 1.) As np.random.choice is used in
    # the agent policy and it expects 1D arrays, we need to remove this singleton batch dimension.
    action_probs = action_probs.squeeze()
    return action_probs

if __name__ == '__main__':
    rand_generator = np.random.RandomState(0)
    action_values = rand_generator.normal(0, 1, (2, 4))
    tau = 0.5

    action_probs = softmax(action_values, tau)
    print("action_probs", action_probs)

    assert (np.allclose(action_probs, np.array([
        [0.25849645, 0.01689625, 0.05374514, 0.67086216],
        [0.84699852, 0.00286345, 0.13520063, 0.01493741]
    ])))

    action_values = np.array([[0.0327, 0.0127, 0.0688]])
    tau = 1.
    action_probs = softmax(action_values, tau)
    print("action_probs", action_probs)

    assert np.allclose(action_probs, np.array([0.3315, 0.3249, 0.3436]), atol=1e-04)

    print("Passed the asserts! (Note: These are however limited in scope, additional testing is encouraged.)")


import os
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

## Machine Learning Online Class - Exercise 1: Linear Regression

def warmUpExercise():
    return np.eye(5)

def plotData(x, y):
    plt.plot(x, y, 'rx', label='Training data')
    plt.xlabel('Population of City in 10,000s')
    plt.ylabel('Profit in $10,000s')
    plt.title('Scatter plot of training data')
    plt.axis([4, 25, -5, 24])

def computeCost(X, y, theta):
    error = (X @ theta - y)
    return (error.T @ error)/ (2*y.size)

def gradientDescent(X, y, theta, alpha, iterations):
    m = y.size
    for _ in range(iterations):
        theta -= alpha/m * X.T @ (X @ theta - y)
    return theta

# ==================== Part 1: Basic Function ====================
print('Running warmUpExercise ... ')
print('5x5 Identity Matrix:')
print(warmUpExercise())
input('Program paused. Press enter to continue.\n')

# ======================= Part 2: Plotting =======================
# x refers to the population size in 10,000s
# y refers to the profit in $10,000s

scriptdir = os.path.dirname(os.path.realpath(__file__))
print(scriptdir)
data = np.loadtxt(scriptdir + '//ex1data1.txt', delimiter=',')
X = data[:,:-1]
y = data[:,-1]
m = y.size #number of training examples

print('Plotting Data ...')
#Plot Data
plotData(X, y)
plt.show()

# =================== Part 3: Cost and Gradient descent ===================
X = np.c_[np.ones(m), X] # #Add a column of ones to X (interception data)
theta = np.zeros(2) #initialize fitting parameters

# Some gradient descent settings
iterations = 1500
alpha = 0.01

print('\nTesting the cost function ...')
# compute and display initial cost
J = computeCost(X, y, theta)
print(f'With theta = [0 ; 0], Cost computed = {J}')
print('Expected cost value (approx) 32.07')

# further testing of the cost function
J = computeCost(X, y, [-1 , 2])
print(f'\nWith theta = [-1 ; 2], Cost computed = {J}')
print(f'Expected cost value (approx) 54.24')
input('Program paused. Press enter to continue.\n')

print('Running Gradient Descent ...')
# run gradient descent
theta = gradientDescent(X, y, theta, alpha, iterations)

# print theta to screen
print('Theta found by gradient descent:')
print(theta)
print('Expected theta values (approx)')
print(' -3.6303,  1.1664\n')

# Plot the linear fit
plotData(X[:,1], y)
plt.plot(X[:,1], X @ theta, '-', label='Linear regression')
plt.legend()
plt.show()

# Predict values for population sizes of 35,000 and 70,000
predict1 = [1, 3.5]  @ theta
print(f'For population = 35,000, we predict a profit of {predict1*10000}')
predict2 = [1, 7] @ theta
print(f'For population = 70,000, we predict a profit of {predict2*10000}')
input('Program paused. Press enter to continue.\n')

# ============= Part 4: Visualizing J(theta_0, theta_1) =============
print('Visualizing J(theta_0, theta_1) ...')

# Grid over which we will calculate J
theta0_vals = np.linspace(-10, 10, 100)
theta1_vals = np.linspace(-1, 4, 100)

# initialize J_vals to a matrix of 0's
J_vals = np.zeros((theta0_vals.size, theta1_vals.size))

# Fill out J_vals
# Because of the way Surface plot and meshgrid work, we need to swap
# j,i order in J_vals, or else the axes will be flipped
for i in range(theta0_vals.size):
    for j in range(theta1_vals.size):
	    J_vals[j,i] = computeCost(X, y, [theta0_vals[i], theta1_vals[j]])

fig = plt.figure()
ax = fig.gca(projection='3d')
# Surface plot
xx, yy = np.meshgrid(theta0_vals, theta1_vals)
surf = ax.plot_surface(xx, yy, J_vals, antialiased=False)
#LaTex rendering with matplotlib is very sow on MacOS
#ax.set_xlabel(r'$\theta_0$')
#ax.set_ylabel(r'$\theta_1$')
ax.set_xlabel('Theta_0')
ax.set_ylabel('Theta_1')
plt.show()

# Contour plot
# Plot J_vals as 15 contours spaced logarithmically between 0.01 and 100
plt.contour(xx, yy, J_vals, np.logspace(-2, 3, 20))
#LaTex rendering with matplotlib is very sow on MacOS
#plt.xlabel(r'$\theta_0$')
#plt.ylabel(r'$\theta_1$')
plt.xlabel('Theta_0')
plt.ylabel('Theta_1')

plt.plot(theta[0], theta[1], 'rx', ms=10, lw=2)
plt.show()


"""This file contains code used in "Think Stats",
by Allen B. Downey, available from greenteapress.com

Copyright 2014 Allen B. Downey
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html
"""

from __future__ import print_function, division

import thinkstats2
import thinkplot

import math
import random
import numpy as np

def MeanError(estimates, actual):
    """Computes the mean error of a sequence of estimates.

    estimate: sequence of numbers
    actual: actual value

    returns: float mean error
    """
    errors = [estimate-actual for estimate in estimates]
    return np.mean(errors)

def RMSE(estimates, actual):
    """Computes the root mean squared error of a sequence of estimates.

    estimate: sequence of numbers
    actual: actual value

    returns: float RMSE
    """
    e2 = [(estimate-actual)**2 for estimate in estimates]
    mse = np.mean(e2)
    return math.sqrt(mse)

def Estimate1(n=7, m=1000):
    """Evaluates RMSE of sample mean and median as estimators.

    n: sample size
    m: number of iterations
    """
    mu = 0
    sigma = 1

    means = []
    medians = []
    for _ in range(m):
        xs = [random.gauss(mu, sigma) for _ in range(n)]
        xbar = np.mean(xs)
        median = np.median(xs)
        means.append(xbar)
        medians.append(median)

    print('Experiment 1')
    print('rmse xbar', RMSE(means, mu))
    print('rmse median', RMSE(medians, mu))

def Estimate2(n=7, m=1000):
    """Evaluates S and Sn-1 as estimators of sample variance.

    n: sample size
    m: number of iterations
    """
    mu = 0
    sigma = 1

    estimates1 = []
    estimates2 = []
    for _ in range(m):
        xs = [random.gauss(mu, sigma) for _ in range(n)]
        biased = np.var(xs)
        unbiased = np.var(xs, ddof=1)
        estimates1.append(biased)
        estimates2.append(unbiased)

    print('Experiment 2')
    print('mean error biased', MeanError(estimates1, sigma**2))
    print('mean error unbiased', MeanError(estimates2, sigma**2))

def Estimate3(n=7, m=1000):
    """Evaluates L and Lm as estimators of the exponential parameter.

    n: sample size
    m: number of iterations
    """
    lam = 2

    means = []
    medians = []
    for _ in range(m):
        xs = np.random.exponential(1/lam, n)
        L = 1 / np.mean(xs)
        Lm = math.log(2) / np.median(xs)
        means.append(L)
        medians.append(Lm)

    print('Experiment 3')
    print('rmse L', RMSE(means, lam))
    print('rmse Lm', RMSE(medians, lam))
    print('mean error L', MeanError(means, lam))
    print('mean error Lm', MeanError(medians, lam))

def SimulateSample(mu=90, sigma=7.5, n=9, m=1000):
    """Plots the sampling distribution of the sample mean.

    mu: hypothetical population mean
    sigma: hypothetical population standard deviation
    n: sample size
    m: number of iterations
    """
    def VertLine(x, y=1):
        thinkplot.Plot([x, x], [0, y], color='0.8', linewidth=3)

    means = []
    for _ in range(m):
        xs = np.random.normal(mu, sigma, n)
        xbar = np.mean(xs)
        means.append(xbar)

    stderr = RMSE(means, mu)
    print('standard error', stderr)

    cdf = thinkstats2.Cdf(means)
    ci = cdf.Percentile(5), cdf.Percentile(95)
    print('confidence interval', ci)
    VertLine(ci[0])
    VertLine(ci[1])

    # plot the CDF
    thinkplot.Cdf(cdf)
    thinkplot.Save(root='estimation1',
                   xlabel='sample mean',
                   ylabel='CDF',
                   title='Sampling distribution')

def main():
    thinkstats2.RandomSeed(17)

    Estimate1()
    Estimate2()
    Estimate3(m=1000)
    SimulateSample()

if __name__ == '__main__':
    main()

DSC 530 - Data Exploration and Analysis/ThinkStats2/code/estimation.py


"""
The Frobenius equation is the Diophantine equation,

a_1 x_1 +... + a_n x_n = b

where a_i> 0 are positive integers, b> 0 is a positive integer, and the solution x_i consists
of non-negative integers. Here is a sample run,

->>> solvefrob([1,2,3,5],10)
 [(0, 0, 0, 2),
  (0, 1, 1, 1),
  (0, 2, 2, 0),
  (0, 5, 0, 0),
  (1, 0, 3, 0),
  (1, 2, 0, 1),
  (1, 3, 1, 0),
  (2, 0, 1, 1),
  (2, 1, 2, 0),
  (2, 4, 0, 0),
  (3, 1, 0, 1),
  (3, 2, 1, 0),
  (4, 0, 2, 0),
  (4, 3, 0, 0),
  (5, 0, 0, 1),
  (5, 1, 1, 0),
  (6, 2, 0, 0),
  (7, 0, 1, 0),
  (8, 1, 0, 0),
  (10, 0, 0, 0)]

Hint: Use Numpy broadcasting effectively. There is a timeout in the test-case,
so if it takes too long to compute (e.g, you used too many for loops), it will be marked wrong.
The function signature is solvefrob(coefs,b) where coefs is the list of a_i coefficients.
You can only use Numpy for this problem. No other third party packages.
"""
import numpy as np
import time

def solvefrob(coefs, b):
    """
    Solves frobenius equation

    Bounding the domain space makes enormous difference in performance
    :param coefs:
    :param b:
    :return:
    """

    # input type validation
    for elem in coefs:
        assert isinstance(elem, int)
        assert elem > 0

    assert isinstance(b, int)
    assert b > 0

    # construct candidate matrix C of dims MxN where N = len(coefs)
    arr = None
    for i, coef in enumerate(coefs[::-1]):
        max = b // coef  # restricts the max value of candidate solution at
        # a given index to bound solution space, vastly increases performance
        r = np.arange(max+1)[:, None]
        if arr is None:
            arr = r
        else:
            new_r = np.repeat(r, len(arr))[:, None]
            new_arr = np.concatenate([arr for _ in range(len(r))])
            arr = np.concatenate([new_r, new_arr], axis=1)

    # extract candidate solutions where C * coefs == b
    out = np.matmul(arr, coefs)
    results = arr[out == b].tolist()
    results = [tuple(result) for result in results]
    return results

if __name__ == '__main__':
    ##### Arguments
    coefs = [1,2,3,5,6,4,6,4]
    b = 15
    ##### End Arguments

    s = time.time()
    results = solvefrob(coefs, b)
    e = time.time()
    print(f"{solvefrob.__name__}, time: {e-s} seconds")
    print(f"Coeffs = {coefs}, B = {b}")
    print(f"number of solutions: {len(results)}\n")
    print("solution | coeffs | dot product")
    print("-"*20)
    for result in results:
        print("{} | {} | {}".format(result, coefs, np.dot(result, coefs)))


import numpy as np

def euclidean_distances(X, Y):
    """Compute pairwise Euclidean distance between the rows of two matrices X (shape MxK)
    and Y (shape NxK). The output of this function is a matrix of shape MxN containing
    the Euclidean distance between two rows.

    Args:
        X {np.ndarray} -- First matrix, containing M examples with K features each.
        Y {np.ndarray} -- Second matrix, containing N examples with K features each.

    Return:
        D {np.ndarray}: MxN matrix with Euclidean distances between rows of X and rows of Y.
    """
    a = X.shape[0]
    b = Y.shape[0]
    D = np.zeros(shape=(a,b))
    n = 0
    for i in X:
        m = 0
        for j in Y:
            elem = np.linalg.norm(i-j)
            D[n][m] = elem
            m += 1
        n += 1
    return D

def manhattan_distances(X, Y):
    """Compute pairwise Manhattan distance between the rows of two matrices X (shape MxK)
    and Y (shape NxK). The output of this function is a matrix of shape MxN containing
    the Manhattan distance between two rows.

    Args:
        X {np.ndarray} -- First matrix, containing M examples with K features each.
        Y {np.ndarray} -- Second matrix, containing N examples with K features each.

    Returns:
        D {np.ndarray}: MxN matrix with Manhattan distances between rows of X and rows of Y.
    """
    a = X.shape[0]
    b = Y.shape[0]
    D = np.zeros(shape=(a,b))
    n = 0
    for i in X:
        m = 0
        for j in Y:
            elem = np.linalg.norm(i-j, ord=1)
            D[n][m] = elem
            m += 1
        n += 1
    return D


import numpy as np
from scipy import stats
def mean_diff_test(a, b, check_col, sample_size=2000):
    ## Define 2 random distributions
    #Sample Size
    a = a[[check_col]]
    b = b[[check_col]]

    ## Calculate the Standard Deviation
    # Calculate the variance to get the standard deviation
    a = np.array(a)
    b = np.array(b)

    a = np.array(a).astype(np.float)
    b = np.array(b).astype(np.float)

    # For unbiased max likelihood estimate we have to divide the var by N-1, and therefore the parameter ddof = 1
    var_a = a.var(ddof=1)
    var_b = b.var(ddof=1)

    # std deviation
    s = np.sqrt((var_a + var_b)/2)

    ## Calculate the t-statistics
    t = (a.mean() - b.mean())/(s*np.sqrt(2/sample_size))

    # Compare with the critical t-value
    #Degrees of freedom
    df = 2 * sample_size - 2

    # p-value after comparison with the t
    p = 1 - stats.t.cdf(t, df=df)

    print("t = " + str(t))
    print("p = " + str(2*p))
    # Note that we multiply the p value by 2 because its a twp tail t-test
    # You can see that after comparing the t statistic with the critical t value (computed internally)
    # we get a good p value of 0.0005 and thus we reject the null hypothesis and thus it proves that the mean of the
    # two distributions are different and statistically significant.

    # Cross Checking with the internal scipy function
    t2, p2 = stats.ttest_ind(a,b)
    print("t = " + str(t2))
    print("p = " + str(2*p2))